From 4ca067e55ce5c0e756bce8cb11496589b8ab3ea0 Mon Sep 17 00:00:00 2001 From: Chirayu Desai Date: Thu, 2 Aug 2012 12:23:05 +0530 Subject: Rename Launcher to Trebuchet Launcher2 is now Trebuchet application_name removed from localized strings and made un-translatable com.android.launcher is now com.cyanogenmod.trebuchet --- Android.mk | 4 +- AndroidManifest.xml | 51 +- fill_screens.py | 4 +- print_db.py | 2 +- proguard.flags | 14 +- res/layout-land/all_apps_cling.xml | 6 +- res/layout-land/application.xml | 2 +- res/layout-land/apps_customize_application.xml | 4 +- res/layout-land/drop_target_bar.xml | 4 +- res/layout-land/folder_cling.xml | 6 +- res/layout-land/folder_icon.xml | 6 +- res/layout-land/hotseat.xml | 8 +- res/layout-land/launcher.xml | 12 +- res/layout-land/search_bar.xml | 4 +- res/layout-land/workspace_cling.xml | 6 +- res/layout-port/all_apps_cling.xml | 6 +- res/layout-port/application.xml | 2 +- res/layout-port/apps_customize_application.xml | 4 +- res/layout-port/drop_target_bar.xml | 4 +- res/layout-port/folder_cling.xml | 6 +- res/layout-port/folder_icon.xml | 6 +- res/layout-port/hotseat.xml | 8 +- res/layout-port/launcher.xml | 12 +- res/layout-port/search_bar.xml | 10 +- res/layout-port/workspace_cling.xml | 6 +- res/layout-sw600dp-port/all_apps_cling.xml | 6 +- res/layout-sw600dp-port/folder_cling.xml | 6 +- res/layout-sw720dp-land/application.xml | 2 +- res/layout-sw720dp-port/application.xml | 2 +- res/layout-sw720dp-port/folder_cling.xml | 6 +- res/layout-sw720dp-port/workspace_cling.xml | 6 +- res/layout-sw720dp/all_apps_cling.xml | 6 +- res/layout-sw720dp/folder_cling.xml | 6 +- res/layout-sw720dp/launcher.xml | 4 +- res/layout-sw720dp/search_bar.xml | 6 +- res/layout-sw720dp/workspace.xml | 6 +- res/layout-sw720dp/workspace_cling.xml | 6 +- res/layout/apps_customize_pane.xml | 10 +- res/layout/apps_customize_widget.xml | 8 +- res/layout/qsb_bar.xml | 4 +- res/layout/tab_widget_indicator.xml | 2 +- res/layout/user_folder.xml | 10 +- res/layout/wallpaper_chooser_base.xml | 2 +- res/layout/workspace_screen.xml | 4 +- res/values-af/strings.xml | 1 - res/values-am/strings.xml | 1 - res/values-ar/strings.xml | 1 - res/values-be/strings.xml | 1 - res/values-bg/strings.xml | 1 - res/values-ca/strings.xml | 1 - res/values-cs/strings.xml | 1 - res/values-da/strings.xml | 1 - res/values-de/strings.xml | 1 - res/values-el/strings.xml | 1 - res/values-en-rGB/strings.xml | 1 - res/values-es-rUS/strings.xml | 1 - res/values-es/strings.xml | 1 - res/values-et/strings.xml | 1 - res/values-fa/strings.xml | 1 - res/values-fi/strings.xml | 1 - res/values-fr/strings.xml | 1 - res/values-hi/strings.xml | 1 - res/values-hr/strings.xml | 1 - res/values-hu/strings.xml | 1 - res/values-in/strings.xml | 1 - res/values-it/strings.xml | 1 - res/values-iw/strings.xml | 1 - res/values-ja/strings.xml | 1 - res/values-ko/strings.xml | 1 - res/values-lt/strings.xml | 1 - res/values-lv/strings.xml | 1 - res/values-ms/strings.xml | 1 - res/values-nb/strings.xml | 1 - res/values-nl/strings.xml | 1 - res/values-pl/strings.xml | 1 - res/values-pt-rPT/strings.xml | 1 - res/values-pt/strings.xml | 1 - res/values-rm/strings.xml | 1 - res/values-ro/strings.xml | 1 - res/values-ru/strings.xml | 1 - res/values-sk/strings.xml | 1 - res/values-sl/strings.xml | 1 - res/values-sr/strings.xml | 1 - res/values-sv/strings.xml | 1 - res/values-th/strings.xml | 1 - res/values-tl/strings.xml | 1 - res/values-tr/strings.xml | 1 - res/values-uk/strings.xml | 1 - res/values-vi/strings.xml | 1 - res/values-zh-rCN/strings.xml | 1 - res/values-zh-rTW/strings.xml | 1 - res/values-zu/strings.xml | 1 - res/values/strings.xml | 6 +- res/xml-sw600dp/default_workspace.xml | 2 +- res/xml-sw720dp/default_workspace.xml | 2 +- res/xml/default_workspace.xml | 2 +- res/xml/update_workspace.xml | 2 +- src/com/android/launcher2/AccessibleTabView.java | 51 - src/com/android/launcher2/AddAdapter.java | 103 - src/com/android/launcher2/Alarm.java | 84 - src/com/android/launcher2/AllAppsList.java | 221 -- src/com/android/launcher2/AllAppsView.java | 48 - .../android/launcher2/AppWidgetResizeFrame.java | 429 --- src/com/android/launcher2/ApplicationInfo.java | 144 - .../android/launcher2/AppsCustomizePagedView.java | 1899 ---------- .../android/launcher2/AppsCustomizeTabHost.java | 488 --- src/com/android/launcher2/BubbleTextView.java | 334 -- src/com/android/launcher2/ButtonDropTarget.java | 147 - src/com/android/launcher2/CellLayout.java | 3022 ---------------- .../android/launcher2/CheckLongPressHelper.java | 62 - src/com/android/launcher2/Cling.java | 278 -- src/com/android/launcher2/DeferredHandler.java | 112 - src/com/android/launcher2/DeleteDropTarget.java | 438 --- src/com/android/launcher2/DragController.java | 809 ----- src/com/android/launcher2/DragLayer.java | 766 ---- src/com/android/launcher2/DragScroller.java | 40 - src/com/android/launcher2/DragSource.java | 45 - src/com/android/launcher2/DragView.java | 292 -- .../android/launcher2/DrawableStateProxyView.java | 69 - src/com/android/launcher2/DropTarget.java | 184 - src/com/android/launcher2/FastBitmapDrawable.java | 108 - src/com/android/launcher2/FocusHelper.java | 898 ----- src/com/android/launcher2/FocusOnlyTabWidget.java | 86 - src/com/android/launcher2/Folder.java | 1105 ------ src/com/android/launcher2/FolderEditText.java | 36 - src/com/android/launcher2/FolderIcon.java | 631 ---- src/com/android/launcher2/FolderInfo.java | 116 - src/com/android/launcher2/HandleView.java | 76 - .../android/launcher2/HolographicImageView.java | 54 - .../android/launcher2/HolographicLinearLayout.java | 85 - .../launcher2/HolographicOutlineHelper.java | 218 -- .../android/launcher2/HolographicViewHelper.java | 84 - src/com/android/launcher2/Hotseat.java | 137 - src/com/android/launcher2/IconCache.java | 229 -- src/com/android/launcher2/InfoDropTarget.java | 127 - .../android/launcher2/InstallShortcutReceiver.java | 252 -- .../android/launcher2/InstallWidgetReceiver.java | 195 - .../launcher2/InterruptibleInOutAnimator.java | 130 - src/com/android/launcher2/ItemInfo.java | 188 - src/com/android/launcher2/Launcher.java | 3710 ------------------- .../launcher2/LauncherAnimatorUpdateListener.java | 30 - .../android/launcher2/LauncherAppWidgetHost.java | 45 - .../launcher2/LauncherAppWidgetHostView.java | 104 - .../android/launcher2/LauncherAppWidgetInfo.java | 98 - src/com/android/launcher2/LauncherApplication.java | 145 - src/com/android/launcher2/LauncherModel.java | 2176 ----------- src/com/android/launcher2/LauncherProvider.java | 1178 ------ src/com/android/launcher2/LauncherSettings.java | 236 -- .../launcher2/LauncherViewPropertyAnimator.java | 255 -- src/com/android/launcher2/PagedView.java | 1913 ---------- src/com/android/launcher2/PagedViewCellLayout.java | 516 --- .../launcher2/PagedViewCellLayoutChildren.java | 160 - src/com/android/launcher2/PagedViewGridLayout.java | 141 - src/com/android/launcher2/PagedViewIcon.java | 92 - src/com/android/launcher2/PagedViewIconCache.java | 133 - src/com/android/launcher2/PagedViewWidget.java | 223 -- .../launcher2/PagedViewWidgetImageView.java | 57 - .../launcher2/PagedViewWithDraggableItems.java | 178 - src/com/android/launcher2/PendingAddItemInfo.java | 104 - src/com/android/launcher2/PreloadReceiver.java | 37 - src/com/android/launcher2/RocketLauncher.java | 417 --- src/com/android/launcher2/SearchDropTargetBar.java | 242 -- .../launcher2/ShortcutAndWidgetContainer.java | 188 - src/com/android/launcher2/ShortcutInfo.java | 173 - src/com/android/launcher2/SmoothPagedView.java | 188 - .../launcher2/SpringLoadedDragController.java | 62 - src/com/android/launcher2/StrokedTextView.java | 138 - .../android/launcher2/SymmetricalLinearTween.java | 118 - src/com/android/launcher2/TweenCallback.java | 24 - .../launcher2/UninstallShortcutReceiver.java | 166 - src/com/android/launcher2/Utilities.java | 278 -- src/com/android/launcher2/WallpaperChooser.java | 47 - .../launcher2/WallpaperChooserDialogFragment.java | 360 -- src/com/android/launcher2/Workspace.java | 3781 -------------------- .../cyanogenmod/trebuchet/AccessibleTabView.java | 51 + src/com/cyanogenmod/trebuchet/AddAdapter.java | 103 + src/com/cyanogenmod/trebuchet/Alarm.java | 84 + src/com/cyanogenmod/trebuchet/AllAppsList.java | 221 ++ src/com/cyanogenmod/trebuchet/AllAppsView.java | 48 + .../trebuchet/AppWidgetResizeFrame.java | 429 +++ src/com/cyanogenmod/trebuchet/ApplicationInfo.java | 144 + .../trebuchet/AppsCustomizePagedView.java | 1899 ++++++++++ .../trebuchet/AppsCustomizeTabHost.java | 488 +++ src/com/cyanogenmod/trebuchet/BubbleTextView.java | 334 ++ .../cyanogenmod/trebuchet/ButtonDropTarget.java | 147 + src/com/cyanogenmod/trebuchet/CellLayout.java | 3022 ++++++++++++++++ .../trebuchet/CheckLongPressHelper.java | 62 + src/com/cyanogenmod/trebuchet/Cling.java | 278 ++ src/com/cyanogenmod/trebuchet/DeferredHandler.java | 112 + .../cyanogenmod/trebuchet/DeleteDropTarget.java | 438 +++ src/com/cyanogenmod/trebuchet/DragController.java | 809 +++++ src/com/cyanogenmod/trebuchet/DragLayer.java | 766 ++++ src/com/cyanogenmod/trebuchet/DragScroller.java | 40 + src/com/cyanogenmod/trebuchet/DragSource.java | 45 + src/com/cyanogenmod/trebuchet/DragView.java | 292 ++ .../trebuchet/DrawableStateProxyView.java | 69 + src/com/cyanogenmod/trebuchet/DropTarget.java | 184 + .../cyanogenmod/trebuchet/FastBitmapDrawable.java | 108 + src/com/cyanogenmod/trebuchet/FocusHelper.java | 898 +++++ .../cyanogenmod/trebuchet/FocusOnlyTabWidget.java | 86 + src/com/cyanogenmod/trebuchet/Folder.java | 1105 ++++++ src/com/cyanogenmod/trebuchet/FolderEditText.java | 36 + src/com/cyanogenmod/trebuchet/FolderIcon.java | 631 ++++ src/com/cyanogenmod/trebuchet/FolderInfo.java | 116 + src/com/cyanogenmod/trebuchet/HandleView.java | 76 + .../trebuchet/HolographicImageView.java | 54 + .../trebuchet/HolographicLinearLayout.java | 85 + .../trebuchet/HolographicOutlineHelper.java | 218 ++ .../trebuchet/HolographicViewHelper.java | 84 + src/com/cyanogenmod/trebuchet/Hotseat.java | 137 + src/com/cyanogenmod/trebuchet/IconCache.java | 229 ++ src/com/cyanogenmod/trebuchet/InfoDropTarget.java | 127 + .../trebuchet/InstallShortcutReceiver.java | 252 ++ .../trebuchet/InstallWidgetReceiver.java | 195 + .../trebuchet/InterruptibleInOutAnimator.java | 130 + src/com/cyanogenmod/trebuchet/ItemInfo.java | 188 + src/com/cyanogenmod/trebuchet/Launcher.java | 3710 +++++++++++++++++++ .../trebuchet/LauncherAnimatorUpdateListener.java | 30 + .../trebuchet/LauncherAppWidgetHost.java | 45 + .../trebuchet/LauncherAppWidgetHostView.java | 104 + .../trebuchet/LauncherAppWidgetInfo.java | 98 + .../cyanogenmod/trebuchet/LauncherApplication.java | 145 + src/com/cyanogenmod/trebuchet/LauncherModel.java | 2176 +++++++++++ .../cyanogenmod/trebuchet/LauncherProvider.java | 1178 ++++++ .../cyanogenmod/trebuchet/LauncherSettings.java | 236 ++ .../trebuchet/LauncherViewPropertyAnimator.java | 255 ++ src/com/cyanogenmod/trebuchet/PagedView.java | 1913 ++++++++++ .../cyanogenmod/trebuchet/PagedViewCellLayout.java | 516 +++ .../trebuchet/PagedViewCellLayoutChildren.java | 160 + .../cyanogenmod/trebuchet/PagedViewGridLayout.java | 141 + src/com/cyanogenmod/trebuchet/PagedViewIcon.java | 92 + .../cyanogenmod/trebuchet/PagedViewIconCache.java | 133 + src/com/cyanogenmod/trebuchet/PagedViewWidget.java | 223 ++ .../trebuchet/PagedViewWidgetImageView.java | 57 + .../trebuchet/PagedViewWithDraggableItems.java | 178 + .../cyanogenmod/trebuchet/PendingAddItemInfo.java | 104 + src/com/cyanogenmod/trebuchet/PreloadReceiver.java | 37 + src/com/cyanogenmod/trebuchet/RocketLauncher.java | 417 +++ .../cyanogenmod/trebuchet/SearchDropTargetBar.java | 242 ++ .../trebuchet/ShortcutAndWidgetContainer.java | 188 + src/com/cyanogenmod/trebuchet/ShortcutInfo.java | 173 + src/com/cyanogenmod/trebuchet/SmoothPagedView.java | 188 + .../trebuchet/SpringLoadedDragController.java | 62 + src/com/cyanogenmod/trebuchet/StrokedTextView.java | 138 + .../trebuchet/SymmetricalLinearTween.java | 118 + src/com/cyanogenmod/trebuchet/TweenCallback.java | 24 + .../trebuchet/UninstallShortcutReceiver.java | 166 + src/com/cyanogenmod/trebuchet/Utilities.java | 278 ++ .../cyanogenmod/trebuchet/WallpaperChooser.java | 47 + .../trebuchet/WallpaperChooserDialogFragment.java | 360 ++ src/com/cyanogenmod/trebuchet/Workspace.java | 3781 ++++++++++++++++++++ tests/stress/Android.mk | 4 +- tests/stress/AndroidManifest.xml | 2 +- .../stress/LauncherRotationStressTest.java | 63 - .../stress/LauncherRotationStressTest.java | 63 + 255 files changed, 32758 insertions(+), 32801 deletions(-) delete mode 100644 src/com/android/launcher2/AccessibleTabView.java delete mode 100644 src/com/android/launcher2/AddAdapter.java delete mode 100644 src/com/android/launcher2/Alarm.java delete mode 100644 src/com/android/launcher2/AllAppsList.java delete mode 100644 src/com/android/launcher2/AllAppsView.java delete mode 100644 src/com/android/launcher2/AppWidgetResizeFrame.java delete mode 100644 src/com/android/launcher2/ApplicationInfo.java delete mode 100644 src/com/android/launcher2/AppsCustomizePagedView.java delete mode 100644 src/com/android/launcher2/AppsCustomizeTabHost.java delete mode 100644 src/com/android/launcher2/BubbleTextView.java delete mode 100644 src/com/android/launcher2/ButtonDropTarget.java delete mode 100644 src/com/android/launcher2/CellLayout.java delete mode 100644 src/com/android/launcher2/CheckLongPressHelper.java delete mode 100644 src/com/android/launcher2/Cling.java delete mode 100644 src/com/android/launcher2/DeferredHandler.java delete mode 100644 src/com/android/launcher2/DeleteDropTarget.java delete mode 100644 src/com/android/launcher2/DragController.java delete mode 100644 src/com/android/launcher2/DragLayer.java delete mode 100644 src/com/android/launcher2/DragScroller.java delete mode 100644 src/com/android/launcher2/DragSource.java delete mode 100644 src/com/android/launcher2/DragView.java delete mode 100644 src/com/android/launcher2/DrawableStateProxyView.java delete mode 100644 src/com/android/launcher2/DropTarget.java delete mode 100644 src/com/android/launcher2/FastBitmapDrawable.java delete mode 100644 src/com/android/launcher2/FocusHelper.java delete mode 100644 src/com/android/launcher2/FocusOnlyTabWidget.java delete mode 100644 src/com/android/launcher2/Folder.java delete mode 100644 src/com/android/launcher2/FolderEditText.java delete mode 100644 src/com/android/launcher2/FolderIcon.java delete mode 100644 src/com/android/launcher2/FolderInfo.java delete mode 100644 src/com/android/launcher2/HandleView.java delete mode 100644 src/com/android/launcher2/HolographicImageView.java delete mode 100644 src/com/android/launcher2/HolographicLinearLayout.java delete mode 100644 src/com/android/launcher2/HolographicOutlineHelper.java delete mode 100644 src/com/android/launcher2/HolographicViewHelper.java delete mode 100644 src/com/android/launcher2/Hotseat.java delete mode 100644 src/com/android/launcher2/IconCache.java delete mode 100644 src/com/android/launcher2/InfoDropTarget.java delete mode 100644 src/com/android/launcher2/InstallShortcutReceiver.java delete mode 100644 src/com/android/launcher2/InstallWidgetReceiver.java delete mode 100644 src/com/android/launcher2/InterruptibleInOutAnimator.java delete mode 100644 src/com/android/launcher2/ItemInfo.java delete mode 100644 src/com/android/launcher2/Launcher.java delete mode 100644 src/com/android/launcher2/LauncherAnimatorUpdateListener.java delete mode 100644 src/com/android/launcher2/LauncherAppWidgetHost.java delete mode 100644 src/com/android/launcher2/LauncherAppWidgetHostView.java delete mode 100644 src/com/android/launcher2/LauncherAppWidgetInfo.java delete mode 100644 src/com/android/launcher2/LauncherApplication.java delete mode 100644 src/com/android/launcher2/LauncherModel.java delete mode 100644 src/com/android/launcher2/LauncherProvider.java delete mode 100644 src/com/android/launcher2/LauncherSettings.java delete mode 100644 src/com/android/launcher2/LauncherViewPropertyAnimator.java delete mode 100644 src/com/android/launcher2/PagedView.java delete mode 100644 src/com/android/launcher2/PagedViewCellLayout.java delete mode 100644 src/com/android/launcher2/PagedViewCellLayoutChildren.java delete mode 100644 src/com/android/launcher2/PagedViewGridLayout.java delete mode 100644 src/com/android/launcher2/PagedViewIcon.java delete mode 100644 src/com/android/launcher2/PagedViewIconCache.java delete mode 100644 src/com/android/launcher2/PagedViewWidget.java delete mode 100644 src/com/android/launcher2/PagedViewWidgetImageView.java delete mode 100644 src/com/android/launcher2/PagedViewWithDraggableItems.java delete mode 100644 src/com/android/launcher2/PendingAddItemInfo.java delete mode 100644 src/com/android/launcher2/PreloadReceiver.java delete mode 100644 src/com/android/launcher2/RocketLauncher.java delete mode 100644 src/com/android/launcher2/SearchDropTargetBar.java delete mode 100644 src/com/android/launcher2/ShortcutAndWidgetContainer.java delete mode 100644 src/com/android/launcher2/ShortcutInfo.java delete mode 100644 src/com/android/launcher2/SmoothPagedView.java delete mode 100644 src/com/android/launcher2/SpringLoadedDragController.java delete mode 100644 src/com/android/launcher2/StrokedTextView.java delete mode 100644 src/com/android/launcher2/SymmetricalLinearTween.java delete mode 100644 src/com/android/launcher2/TweenCallback.java delete mode 100644 src/com/android/launcher2/UninstallShortcutReceiver.java delete mode 100644 src/com/android/launcher2/Utilities.java delete mode 100644 src/com/android/launcher2/WallpaperChooser.java delete mode 100644 src/com/android/launcher2/WallpaperChooserDialogFragment.java delete mode 100644 src/com/android/launcher2/Workspace.java create mode 100644 src/com/cyanogenmod/trebuchet/AccessibleTabView.java create mode 100644 src/com/cyanogenmod/trebuchet/AddAdapter.java create mode 100644 src/com/cyanogenmod/trebuchet/Alarm.java create mode 100644 src/com/cyanogenmod/trebuchet/AllAppsList.java create mode 100644 src/com/cyanogenmod/trebuchet/AllAppsView.java create mode 100644 src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java create mode 100644 src/com/cyanogenmod/trebuchet/ApplicationInfo.java create mode 100644 src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java create mode 100644 src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java create mode 100644 src/com/cyanogenmod/trebuchet/BubbleTextView.java create mode 100644 src/com/cyanogenmod/trebuchet/ButtonDropTarget.java create mode 100644 src/com/cyanogenmod/trebuchet/CellLayout.java create mode 100644 src/com/cyanogenmod/trebuchet/CheckLongPressHelper.java create mode 100644 src/com/cyanogenmod/trebuchet/Cling.java create mode 100644 src/com/cyanogenmod/trebuchet/DeferredHandler.java create mode 100644 src/com/cyanogenmod/trebuchet/DeleteDropTarget.java create mode 100644 src/com/cyanogenmod/trebuchet/DragController.java create mode 100644 src/com/cyanogenmod/trebuchet/DragLayer.java create mode 100644 src/com/cyanogenmod/trebuchet/DragScroller.java create mode 100644 src/com/cyanogenmod/trebuchet/DragSource.java create mode 100644 src/com/cyanogenmod/trebuchet/DragView.java create mode 100644 src/com/cyanogenmod/trebuchet/DrawableStateProxyView.java create mode 100644 src/com/cyanogenmod/trebuchet/DropTarget.java create mode 100644 src/com/cyanogenmod/trebuchet/FastBitmapDrawable.java create mode 100644 src/com/cyanogenmod/trebuchet/FocusHelper.java create mode 100644 src/com/cyanogenmod/trebuchet/FocusOnlyTabWidget.java create mode 100644 src/com/cyanogenmod/trebuchet/Folder.java create mode 100644 src/com/cyanogenmod/trebuchet/FolderEditText.java create mode 100644 src/com/cyanogenmod/trebuchet/FolderIcon.java create mode 100644 src/com/cyanogenmod/trebuchet/FolderInfo.java create mode 100644 src/com/cyanogenmod/trebuchet/HandleView.java create mode 100644 src/com/cyanogenmod/trebuchet/HolographicImageView.java create mode 100644 src/com/cyanogenmod/trebuchet/HolographicLinearLayout.java create mode 100644 src/com/cyanogenmod/trebuchet/HolographicOutlineHelper.java create mode 100644 src/com/cyanogenmod/trebuchet/HolographicViewHelper.java create mode 100644 src/com/cyanogenmod/trebuchet/Hotseat.java create mode 100644 src/com/cyanogenmod/trebuchet/IconCache.java create mode 100644 src/com/cyanogenmod/trebuchet/InfoDropTarget.java create mode 100644 src/com/cyanogenmod/trebuchet/InstallShortcutReceiver.java create mode 100644 src/com/cyanogenmod/trebuchet/InstallWidgetReceiver.java create mode 100644 src/com/cyanogenmod/trebuchet/InterruptibleInOutAnimator.java create mode 100644 src/com/cyanogenmod/trebuchet/ItemInfo.java create mode 100644 src/com/cyanogenmod/trebuchet/Launcher.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherAnimatorUpdateListener.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherAppWidgetHost.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherAppWidgetHostView.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherApplication.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherModel.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherProvider.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherSettings.java create mode 100644 src/com/cyanogenmod/trebuchet/LauncherViewPropertyAnimator.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedView.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewCellLayout.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewCellLayoutChildren.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewGridLayout.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewIcon.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewIconCache.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewWidget.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewWidgetImageView.java create mode 100644 src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java create mode 100644 src/com/cyanogenmod/trebuchet/PendingAddItemInfo.java create mode 100644 src/com/cyanogenmod/trebuchet/PreloadReceiver.java create mode 100644 src/com/cyanogenmod/trebuchet/RocketLauncher.java create mode 100644 src/com/cyanogenmod/trebuchet/SearchDropTargetBar.java create mode 100644 src/com/cyanogenmod/trebuchet/ShortcutAndWidgetContainer.java create mode 100644 src/com/cyanogenmod/trebuchet/ShortcutInfo.java create mode 100644 src/com/cyanogenmod/trebuchet/SmoothPagedView.java create mode 100644 src/com/cyanogenmod/trebuchet/SpringLoadedDragController.java create mode 100644 src/com/cyanogenmod/trebuchet/StrokedTextView.java create mode 100644 src/com/cyanogenmod/trebuchet/SymmetricalLinearTween.java create mode 100644 src/com/cyanogenmod/trebuchet/TweenCallback.java create mode 100644 src/com/cyanogenmod/trebuchet/UninstallShortcutReceiver.java create mode 100644 src/com/cyanogenmod/trebuchet/Utilities.java create mode 100644 src/com/cyanogenmod/trebuchet/WallpaperChooser.java create mode 100644 src/com/cyanogenmod/trebuchet/WallpaperChooserDialogFragment.java create mode 100644 src/com/cyanogenmod/trebuchet/Workspace.java delete mode 100644 tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java create mode 100644 tests/stress/src/com/cyanogenmod/trebuchet/stress/LauncherRotationStressTest.java diff --git a/Android.mk b/Android.mk index abcca40c9..e05cd4172 100644 --- a/Android.mk +++ b/Android.mk @@ -24,10 +24,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := android-common android-support-v13 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) -LOCAL_PACKAGE_NAME := Launcher2 +LOCAL_PACKAGE_NAME := Trebuchet LOCAL_CERTIFICATE := shared -LOCAL_OVERRIDES_PACKAGES := Home +LOCAL_OVERRIDES_PACKAGES := Home Launcher2 LOCAL_PROGUARD_FLAG_FILES := proguard.flags diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dbeb7bce1..5b6d4fdef 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -19,34 +19,35 @@ --> + package="com.cyanogenmod.trebuchet" + android:versionName="@string/application_version"> - - + + - @@ -110,37 +111,37 @@ + android:name="com.cyanogenmod.trebuchet.PreloadReceiver" + android:permission="com.cyanogenmod.trebuchet.permission.PRELOAD_WORKSPACE"> - + + android:name="com.cyanogenmod.trebuchet.InstallShortcutReceiver" + android:permission="com.cyanogenmod.trebuchet.permission.INSTALL_SHORTCUT"> - + + android:name="com.cyanogenmod.trebuchet.UninstallShortcutReceiver" + android:permission="com.cyanogenmod.trebuchet.permission.UNINSTALL_SHORTCUT"> - + + android:name="com.cyanogenmod.trebuchet.LauncherProvider" + android:authorities="com.cyanogenmod.trebuchet.settings" + android:writePermission="com.cyanogenmod.trebuchet.permission.WRITE_SETTINGS" + android:readPermission="com.cyanogenmod.trebuchet.permission.READ_SETTINGS" /> diff --git a/fill_screens.py b/fill_screens.py index 5841b8e49..c4badb9f6 100755 --- a/fill_screens.py +++ b/fill_screens.py @@ -34,7 +34,7 @@ def make_dir(): def pull_file(fn): print "pull_file: " + fn rv = os.system("adb pull" - + " /data/data/com.android.launcher/databases/launcher.db" + + " /data/data/com.cyanogenmod.trebuchet/databases/launcher.db" + " " + fn); if rv != 0: print "adb pull failed" @@ -44,7 +44,7 @@ def push_file(fn): print "push_file: " + fn rv = os.system("adb push" + " " + fn - + " /data/data/com.android.launcher/databases/launcher.db") + + " /data/data/com.cyanogenmod.trebuchet/databases/launcher.db") if rv != 0: print "adb push failed" sys.exit(1) diff --git a/print_db.py b/print_db.py index 4f90f94f1..b5bb8d4a5 100755 --- a/print_db.py +++ b/print_db.py @@ -32,7 +32,7 @@ def make_dir(): def pull_file(fn): print "pull_file: " + fn rv = os.system("adb pull" - + " /data/data/com.android.launcher/databases/launcher.db" + + " /data/data/com.cyanogenmod.trebuchet/databases/launcher.db" + " " + fn); if rv != 0: print "adb pull failed" diff --git a/proguard.flags b/proguard.flags index 0cde28ec1..4a279151b 100644 --- a/proguard.flags +++ b/proguard.flags @@ -1,4 +1,4 @@ --keep class com.android.launcher2.Launcher { +-keep class com.cyanogenmod.trebuchet.Launcher { public void previousScreen(android.view.View); public void nextScreen(android.view.View); public void launchHotSeat(android.view.View); @@ -11,7 +11,7 @@ public void dismissAllAppsCling(android.view.View); } --keep class com.android.launcher2.CellLayout { +-keep class com.cyanogenmod.trebuchet.CellLayout { public float getBackgroundAlpha(); public void setBackgroundAlpha(float); public float getDimmableProgress(); @@ -22,7 +22,7 @@ public void setHoverAlpha(float); } --keep class com.android.launcher2.DragLayer$LayoutParams { +-keep class com.cyanogenmod.trebuchet.DragLayer$LayoutParams { public void setWidth(int); public int getWidth(); public void setHeight(int); @@ -33,7 +33,7 @@ public int getY(); } --keep class com.android.launcher2.CellLayout$LayoutParams { +-keep class com.cyanogenmod.trebuchet.CellLayout$LayoutParams { public void setWidth(int); public int getWidth(); public void setHeight(int); @@ -44,7 +44,7 @@ public int getY(); } --keep class com.android.launcher2.Workspace { +-keep class com.cyanogenmod.trebuchet.Workspace { public float getBackgroundAlpha(); public void setBackgroundAlpha(float); public float getChildrenOutlineAlpha(); @@ -55,10 +55,10 @@ public float getHorizontalWallpaperOffset(); } --keep class com.android.launcher2.AllApps3D$Defines { +-keep class com.cyanogenmod.trebuchet.AllApps3D$Defines { *; } --keep class com.android.launcher2.ClippedImageView { +-keep class com.cyanogenmod.trebuchet.ClippedImageView { *; } diff --git a/res/layout-land/all_apps_cling.xml b/res/layout-land/all_apps_cling.xml index e89d4c178..e8bd5fa02 100644 --- a/res/layout-land/all_apps_cling.xml +++ b/res/layout-land/all_apps_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-land/application.xml b/res/layout-land/application.xml index 9ed1fa115..422ecbf4a 100644 --- a/res/layout-land/application.xml +++ b/res/layout-land/application.xml @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/res/layout-land/apps_customize_application.xml b/res/layout-land/apps_customize_application.xml index ba95b27a6..e79ab75e4 100644 --- a/res/layout-land/apps_customize_application.xml +++ b/res/layout-land/apps_customize_application.xml @@ -14,9 +14,9 @@ limitations under the License. --> - - @@ -29,7 +29,7 @@ style="@style/DropTargetButtonContainer" android:layout_weight="1"> - diff --git a/res/layout-land/folder_cling.xml b/res/layout-land/folder_cling.xml index 3d7f4f459..2fa73fe26 100644 --- a/res/layout-land/folder_cling.xml +++ b/res/layout-land/folder_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-land/folder_icon.xml b/res/layout-land/folder_icon.xml index 808ff5e7b..5e4126f72 100644 --- a/res/layout-land/folder_icon.xml +++ b/res/layout-land/folder_icon.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - - + diff --git a/res/layout-land/hotseat.xml b/res/layout-land/hotseat.xml index d75919671..2b1334762 100644 --- a/res/layout-land/hotseat.xml +++ b/res/layout-land/hotseat.xml @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 6b65f5666..ff25506ce 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -14,9 +14,9 @@ limitations under the License. --> - - - + - - + diff --git a/res/layout-land/search_bar.xml b/res/layout-land/search_bar.xml index 88b7bbf4e..25040c11f 100644 --- a/res/layout-land/search_bar.xml +++ b/res/layout-land/search_bar.xml @@ -18,7 +18,7 @@ android:layout_width="@dimen/qsb_bar_height" android:layout_height="match_parent"> - - - - + diff --git a/res/layout-port/all_apps_cling.xml b/res/layout-port/all_apps_cling.xml index 09414ce53..ed188e7cf 100644 --- a/res/layout-port/all_apps_cling.xml +++ b/res/layout-port/all_apps_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-port/application.xml b/res/layout-port/application.xml index ddc835420..6e2e756d5 100644 --- a/res/layout-port/application.xml +++ b/res/layout-port/application.xml @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/res/layout-port/apps_customize_application.xml b/res/layout-port/apps_customize_application.xml index 84a8712d7..213fe21f7 100644 --- a/res/layout-port/apps_customize_application.xml +++ b/res/layout-port/apps_customize_application.xml @@ -14,9 +14,9 @@ limitations under the License. --> - - - - - + diff --git a/res/layout-port/folder_icon.xml b/res/layout-port/folder_icon.xml index 5ee1327a3..523edf93c 100644 --- a/res/layout-port/folder_icon.xml +++ b/res/layout-port/folder_icon.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - - + diff --git a/res/layout-port/hotseat.xml b/res/layout-port/hotseat.xml index 376173ae9..0420469eb 100644 --- a/res/layout-port/hotseat.xml +++ b/res/layout-port/hotseat.xml @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index a4275010b..6f7961779 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -14,9 +14,9 @@ limitations under the License. --> - - - + - - + diff --git a/res/layout-port/search_bar.xml b/res/layout-port/search_bar.xml index 85da2f155..c069c43e0 100644 --- a/res/layout-port/search_bar.xml +++ b/res/layout-port/search_bar.xml @@ -15,14 +15,14 @@ --> - - + - - + diff --git a/res/layout-port/workspace_cling.xml b/res/layout-port/workspace_cling.xml index 10e697620..03f317920 100644 --- a/res/layout-port/workspace_cling.xml +++ b/res/layout-port/workspace_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw600dp-port/all_apps_cling.xml b/res/layout-sw600dp-port/all_apps_cling.xml index 049822408..4ae4a0a4c 100644 --- a/res/layout-sw600dp-port/all_apps_cling.xml +++ b/res/layout-sw600dp-port/all_apps_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw600dp-port/folder_cling.xml b/res/layout-sw600dp-port/folder_cling.xml index e3a9caa22..5f9495b57 100644 --- a/res/layout-sw600dp-port/folder_cling.xml +++ b/res/layout-sw600dp-port/folder_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw720dp-land/application.xml b/res/layout-sw720dp-land/application.xml index 9393f7e40..e6e681dc2 100644 --- a/res/layout-sw720dp-land/application.xml +++ b/res/layout-sw720dp-land/application.xml @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/res/layout-sw720dp-port/application.xml b/res/layout-sw720dp-port/application.xml index af7a8a4df..a849355e8 100644 --- a/res/layout-sw720dp-port/application.xml +++ b/res/layout-sw720dp-port/application.xml @@ -14,5 +14,5 @@ limitations under the License. --> - diff --git a/res/layout-sw720dp-port/folder_cling.xml b/res/layout-sw720dp-port/folder_cling.xml index 017d0fde9..3a11dff77 100644 --- a/res/layout-sw720dp-port/folder_cling.xml +++ b/res/layout-sw720dp-port/folder_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw720dp-port/workspace_cling.xml b/res/layout-sw720dp-port/workspace_cling.xml index 29bea810d..cf9b614dd 100644 --- a/res/layout-sw720dp-port/workspace_cling.xml +++ b/res/layout-sw720dp-port/workspace_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw720dp/all_apps_cling.xml b/res/layout-sw720dp/all_apps_cling.xml index 7079b2107..b2f0f6f05 100644 --- a/res/layout-sw720dp/all_apps_cling.xml +++ b/res/layout-sw720dp/all_apps_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw720dp/folder_cling.xml b/res/layout-sw720dp/folder_cling.xml index 4188804c2..7ab4cb1d5 100644 --- a/res/layout-sw720dp/folder_cling.xml +++ b/res/layout-sw720dp/folder_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml index 65149f29a..d5f36accb 100644 --- a/res/layout-sw720dp/launcher.xml +++ b/res/layout-sw720dp/launcher.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - + diff --git a/res/layout-sw720dp/search_bar.xml b/res/layout-sw720dp/search_bar.xml index 55ec959de..b4c948271 100644 --- a/res/layout-sw720dp/search_bar.xml +++ b/res/layout-sw720dp/search_bar.xml @@ -19,7 +19,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout-sw720dp/workspace_cling.xml b/res/layout-sw720dp/workspace_cling.xml index fd7d4f8b5..959a4f0c4 100644 --- a/res/layout-sw720dp/workspace_cling.xml +++ b/res/layout-sw720dp/workspace_cling.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml index efd29a673..f41cd34f3 100644 --- a/res/layout/apps_customize_pane.xml +++ b/res/layout/apps_customize_pane.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - + diff --git a/res/layout/apps_customize_widget.xml b/res/layout/apps_customize_widget.xml index abb7508b5..76bcd773b 100644 --- a/res/layout/apps_customize_widget.xml +++ b/res/layout/apps_customize_widget.xml @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + diff --git a/res/layout/qsb_bar.xml b/res/layout/qsb_bar.xml index 322dc006f..563026df1 100644 --- a/res/layout/qsb_bar.xml +++ b/res/layout/qsb_bar.xml @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - @@ -30,4 +30,4 @@ - + diff --git a/res/layout/tab_widget_indicator.xml b/res/layout/tab_widget_indicator.xml index df43d3ddd..9a14032b2 100644 --- a/res/layout/tab_widget_indicator.xml +++ b/res/layout/tab_widget_indicator.xml @@ -14,6 +14,6 @@ limitations under the License. --> - diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml index 208390d1e..cd63b1954 100644 --- a/res/layout/user_folder.xml +++ b/res/layout/user_folder.xml @@ -14,15 +14,15 @@ limitations under the License. --> - - - - + diff --git a/res/layout/wallpaper_chooser_base.xml b/res/layout/wallpaper_chooser_base.xml index fa8ea9372..cf149c0db 100644 --- a/res/layout/wallpaper_chooser_base.xml +++ b/res/layout/wallpaper_chooser_base.xml @@ -21,7 +21,7 @@ - diff --git a/res/layout/workspace_screen.xml b/res/layout/workspace_screen.xml index d6e66f69d..37b11dea5 100644 --- a/res/layout/workspace_screen.xml +++ b/res/layout/workspace_screen.xml @@ -14,9 +14,9 @@ limitations under the License. --> - - "Lanseerpoort" "Tuis" "Android Kernprogramme" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index 2319638be..6fd0c86df 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -19,7 +19,6 @@ - "አስነሺ" "መነሻ" "የAndroid ኮር ትግበራዎች" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 0ddd36970..dd047208e 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -19,7 +19,6 @@ - "المشغل" "المنزل" "تطبيقات Android المركزية" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 150434f40..333bfc889 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -19,7 +19,6 @@ - "Панэль запуску" "На галоўную старонку" "Асноўныя прыкладанні для Android" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index f435ff65a..0e5a62076 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -19,7 +19,6 @@ - "Стартов панел" "Начало" "Основни приложения на Android" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 6ed84db92..c5b9c4815 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -19,7 +19,6 @@ - "Barra d\'execució ràpida" "Pàgina d\'inici" "Aplicacions principals d\'Android" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index dea19f6c5..18d63e9ca 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Plocha" "Android Core Apps" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index af00d825b..7e123c5d1 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Startside" "Android-kerneprogrammer" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 0b01e4272..ed3872d00 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -19,7 +19,6 @@ - "Übersicht" "Startbildschirm" "Android Core Apps" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 78e153ab0..23de9233c 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Αρχική σελίδα" "Βασικές εφαρμογές Android" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index ceb215a73..4ed5f7a61 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Home" "Android Core Apps" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index bc6566dfb..f7b2cbb6b 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Casa" "Aplicaciones del núcleo de Android" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 2987f3e51..199d72cab 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Casa" "Aplicaciones básicas de Android" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 78c983a38..67c30bf78 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -19,7 +19,6 @@ - "Käivitaja" "Kodu" "Androidi tuumrakendused" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index d03e82a09..607803a1d 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -19,7 +19,6 @@ - "راه انداز" "صفحه اصلی" "برنامه های Android Core" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 29f38267f..adf68e80d 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -19,7 +19,6 @@ - "Käynnistysohjelma" "Aloitusruutu" "Android Core -sovellukset" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 43bf8379e..348c6d44b 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -19,7 +19,6 @@ - "Lanceur d\'applications" "Accueil" "Applications de base Android" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index c5ae89a44..f481e41ce 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -19,7 +19,6 @@ - "लॉन्चर" "मुखपृष्ठ" "Android मुख्य एप्लिकेशन" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 665d7b3ff..c10e827ed 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -19,7 +19,6 @@ - "Pokretač" "Početna" "Matične aplikacije za Android" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 761ee7217..f090f133d 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -19,7 +19,6 @@ - "Indító" "Főoldal" "Alap Android-alkalmazások" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 3ba622584..24c5c9e78 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -19,7 +19,6 @@ - "Peluncur" "Beranda" "Android Core Apps" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 9a73872e6..ece2e9e01 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -19,7 +19,6 @@ - "Avvio applicazioni" "Home" "Android Core Apps" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 8dd6cd933..be4a64b22 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -19,7 +19,6 @@ - "מפעיל" "בית" "יישומי ליבה של Android" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 7e29730f9..756de5218 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -19,7 +19,6 @@ - "ランチャー" "Home" "Android Core Apps" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 990dcfb63..34515daaf 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "홈" "Android Core 애플리케이션" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 885ecf0cf..9f0638eed 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -19,7 +19,6 @@ - "Paleidimo priemonė" "Pagrindinis" "Pagrindinės „Android“ programos" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index c1cd54535..ab730923a 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -19,7 +19,6 @@ - "Palaidējs" "Sākums" "Android kodola lietojumprogrammas" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index 3f2d0d84b..11a5cc466 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -19,7 +19,6 @@ - "Pelancar" "Laman Utama" "Apl Teras Android" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index a3455165a..e7bbc3466 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -19,7 +19,6 @@ - "Utskytingsrampe" "Home" "Android-kjerneapplikasjoner" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 457ac307d..41e91159d 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Startpagina" "Android-kerntoepassingen" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 20b421737..12596ab08 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -19,7 +19,6 @@ - "Program uruchamiający" "Strona główna" "Aplikacje główne systemu Android" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index d7cedc09f..94025d519 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Página Inicial" "Aplicações Principais do Android" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 53468bfe4..c289305b6 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Início" "Principais aplicativos do Android" diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml index 99f72bb1e..34d47c8bb 100644 --- a/res/values-rm/strings.xml +++ b/res/values-rm/strings.xml @@ -19,7 +19,6 @@ - "Lantschader" "Applicaziuns da basa dad Android" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 1ed96ac5b..8b1cac315 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -19,7 +19,6 @@ - "Lansator" "Ecran de pornire" "Android Core Apps" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index c2b88d519..765fa02a1 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Главный экран" "Основные приложения" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 43c4c1675..d648dc21e 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -19,7 +19,6 @@ - "Spúšťač" "Plocha" "Android Core Apps" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 3217c0a18..f5af01ccf 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -19,7 +19,6 @@ - "Zaganjalnik" "Začetna stran" "Android Core Apps" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 5dd726a82..7c0a00dee 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -19,7 +19,6 @@ - "Покретач" "Кућни" "Основне Android апликације" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 067b5f10b..aed344085 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -19,7 +19,6 @@ - "Startbild" "Startsida" "Android Core Apps" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 91070ad77..3deb934aa 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -19,7 +19,6 @@ - "ตัวเรียกใช้งาน" "หน้าแรก" "แอปหลัก Android" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index dfa535754..6c68bb5df 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Home" "Android Core Apps" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index d9ce91529..0e5100b1c 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -19,7 +19,6 @@ - "Launcher" "Ana Ekran" "Android Core Apps" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 5031ea996..5f9eaf0fe 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -19,7 +19,6 @@ - "Пан. запуску" "Домашня сторінка" "Служби Android Core" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 232aa0a13..d77c5a549 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -19,7 +19,6 @@ - "Trình khởi chạy" "Trang chủ" "Ứng dụng Lõi Android" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index f5eeab19c..7d34eedba 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -19,7 +19,6 @@ - "启动器" "主屏幕" "Android 核心应用" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index f261ddfb6..f952277f5 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -19,7 +19,6 @@ - "啟動器" "住家" "Android 核心應用程式" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 6faa63d01..ed1e57de6 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -19,7 +19,6 @@ - "Isiqalisi" "Ikhaya" "I-Android Core Apps" diff --git a/res/values/strings.xml b/res/values/strings.xml index 4aee339e4..69d2055f7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -21,7 +21,11 @@ - Launcher + Trebuchet + + Copyright \u00A9 CyanogenMod 2012 + + 0.1 Home diff --git a/res/xml-sw600dp/default_workspace.xml b/res/xml-sw600dp/default_workspace.xml index 3afb3b755..bf5060546 100644 --- a/res/xml-sw600dp/default_workspace.xml +++ b/res/xml-sw600dp/default_workspace.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + diff --git a/res/xml-sw720dp/default_workspace.xml b/res/xml-sw720dp/default_workspace.xml index 6302d7e04..e6651aae2 100644 --- a/res/xml-sw720dp/default_workspace.xml +++ b/res/xml-sw720dp/default_workspace.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml index f2f4240ab..4c50d8481 100644 --- a/res/xml/default_workspace.xml +++ b/res/xml/default_workspace.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + diff --git a/res/xml/update_workspace.xml b/res/xml/update_workspace.xml index 44a3f9efa..5c2547988 100644 --- a/res/xml/update_workspace.xml +++ b/res/xml/update_workspace.xml @@ -14,7 +14,7 @@ limitations under the License. --> - + diff --git a/src/com/android/launcher2/AccessibleTabView.java b/src/com/android/launcher2/AccessibleTabView.java deleted file mode 100644 index 101f139e7..000000000 --- a/src/com/android/launcher2/AccessibleTabView.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.widget.TextView; - -/** - * We use a custom tab view to process our own focus traversals. - */ -public class AccessibleTabView extends TextView { - public AccessibleTabView(Context context) { - super(context); - } - - public AccessibleTabView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AccessibleTabView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return FocusHelper.handleTabKeyEvent(this, keyCode, event) - || super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return FocusHelper.handleTabKeyEvent(this, keyCode, event) - || super.onKeyUp(keyCode, event); - } -} diff --git a/src/com/android/launcher2/AddAdapter.java b/src/com/android/launcher2/AddAdapter.java deleted file mode 100644 index c2a424b00..000000000 --- a/src/com/android/launcher2/AddAdapter.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import java.util.ArrayList; - -import com.android.launcher.R; - -/** - * Adapter showing the types of items that can be added to a {@link Workspace}. - */ -public class AddAdapter extends BaseAdapter { - - private final LayoutInflater mInflater; - - private final ArrayList mItems = new ArrayList(); - - public static final int ITEM_SHORTCUT = 0; - public static final int ITEM_APPWIDGET = 1; - public static final int ITEM_APPLICATION = 2; - public static final int ITEM_WALLPAPER = 3; - - /** - * Specific item in our list. - */ - public class ListItem { - public final CharSequence text; - public final Drawable image; - public final int actionTag; - - public ListItem(Resources res, int textResourceId, int imageResourceId, int actionTag) { - text = res.getString(textResourceId); - if (imageResourceId != -1) { - image = res.getDrawable(imageResourceId); - } else { - image = null; - } - this.actionTag = actionTag; - } - } - - public AddAdapter(Launcher launcher) { - super(); - - mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - // Create default actions - Resources res = launcher.getResources(); - - mItems.add(new ListItem(res, R.string.group_wallpapers, - R.drawable.ic_launcher_wallpaper, ITEM_WALLPAPER)); - } - - public View getView(int position, View convertView, ViewGroup parent) { - ListItem item = (ListItem) getItem(position); - - if (convertView == null) { - convertView = mInflater.inflate(R.layout.add_list_item, parent, false); - } - - TextView textView = (TextView) convertView; - textView.setTag(item); - textView.setText(item.text); - textView.setCompoundDrawablesWithIntrinsicBounds(item.image, null, null, null); - - return convertView; - } - - public int getCount() { - return mItems.size(); - } - - public Object getItem(int position) { - return mItems.get(position); - } - - public long getItemId(int position) { - return position; - } -} diff --git a/src/com/android/launcher2/Alarm.java b/src/com/android/launcher2/Alarm.java deleted file mode 100644 index 7cd21c327..000000000 --- a/src/com/android/launcher2/Alarm.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.os.Handler; - -public class Alarm implements Runnable{ - // if we reach this time and the alarm hasn't been cancelled, call the listener - private long mAlarmTriggerTime; - - // if we've scheduled a call to run() (ie called mHandler.postDelayed), this variable is true. - // We use this to avoid having multiple pending callbacks - private boolean mWaitingForCallback; - - private Handler mHandler; - private OnAlarmListener mAlarmListener; - private boolean mAlarmPending = false; - - public Alarm() { - mHandler = new Handler(); - } - - public void setOnAlarmListener(OnAlarmListener alarmListener) { - mAlarmListener = alarmListener; - } - - // Sets the alarm to go off in a certain number of milliseconds. If the alarm is already set, - // it's overwritten and only the new alarm setting is used - public void setAlarm(long millisecondsInFuture) { - long currentTime = System.currentTimeMillis(); - mAlarmPending = true; - mAlarmTriggerTime = currentTime + millisecondsInFuture; - if (!mWaitingForCallback) { - mHandler.postDelayed(this, mAlarmTriggerTime - currentTime); - mWaitingForCallback = true; - } - } - - public void cancelAlarm() { - mAlarmTriggerTime = 0; - mAlarmPending = false; - } - - // this is called when our timer runs out - public void run() { - mWaitingForCallback = false; - if (mAlarmTriggerTime != 0) { - long currentTime = System.currentTimeMillis(); - if (mAlarmTriggerTime > currentTime) { - // We still need to wait some time to trigger spring loaded mode-- - // post a new callback - mHandler.postDelayed(this, Math.max(0, mAlarmTriggerTime - currentTime)); - mWaitingForCallback = true; - } else { - mAlarmPending = false; - if (mAlarmListener != null) { - mAlarmListener.onAlarm(this); - } - } - } - } - - public boolean alarmPending() { - return mAlarmPending; - } -} - -interface OnAlarmListener { - public void onAlarm(Alarm alarm); -} diff --git a/src/com/android/launcher2/AllAppsList.java b/src/com/android/launcher2/AllAppsList.java deleted file mode 100644 index 051b0bd1d..000000000 --- a/src/com/android/launcher2/AllAppsList.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import java.util.ArrayList; -import java.util.List; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; - - -/** - * Stores the list of all applications for the all apps view. - */ -class AllAppsList { - public static final int DEFAULT_APPLICATIONS_NUMBER = 42; - - /** The list off all apps. */ - public ArrayList data = - new ArrayList(DEFAULT_APPLICATIONS_NUMBER); - /** The list of apps that have been added since the last notify() call. */ - public ArrayList added = - new ArrayList(DEFAULT_APPLICATIONS_NUMBER); - /** The list of apps that have been removed since the last notify() call. */ - public ArrayList removed = new ArrayList(); - /** The list of apps that have been modified since the last notify() call. */ - public ArrayList modified = new ArrayList(); - - private IconCache mIconCache; - - /** - * Boring constructor. - */ - public AllAppsList(IconCache iconCache) { - mIconCache = iconCache; - } - - /** - * Add the supplied ApplicationInfo objects to the list, and enqueue it into the - * list to broadcast when notify() is called. - * - * If the app is already in the list, doesn't add it. - */ - public void add(ApplicationInfo info) { - if (findActivity(data, info.componentName)) { - return; - } - data.add(info); - added.add(info); - } - - public void clear() { - data.clear(); - // TODO: do we clear these too? - added.clear(); - removed.clear(); - modified.clear(); - } - - public int size() { - return data.size(); - } - - public ApplicationInfo get(int index) { - return data.get(index); - } - - /** - * Add the icons for the supplied apk called packageName. - */ - public void addPackage(Context context, String packageName) { - final List matches = findActivitiesForPackage(context, packageName); - - if (matches.size() > 0) { - for (ResolveInfo info : matches) { - add(new ApplicationInfo(context.getPackageManager(), info, mIconCache, null)); - } - } - } - - /** - * Remove the apps for the given apk identified by packageName. - */ - public void removePackage(String packageName) { - final List data = this.data; - for (int i = data.size() - 1; i >= 0; i--) { - ApplicationInfo info = data.get(i); - final ComponentName component = info.intent.getComponent(); - if (packageName.equals(component.getPackageName())) { - removed.add(info); - data.remove(i); - } - } - // This is more aggressive than it needs to be. - mIconCache.flush(); - } - - /** - * Add and remove icons for this package which has been updated. - */ - public void updatePackage(Context context, String packageName) { - final List matches = findActivitiesForPackage(context, packageName); - if (matches.size() > 0) { - // Find disabled/removed activities and remove them from data and add them - // to the removed list. - for (int i = data.size() - 1; i >= 0; i--) { - final ApplicationInfo applicationInfo = data.get(i); - final ComponentName component = applicationInfo.intent.getComponent(); - if (packageName.equals(component.getPackageName())) { - if (!findActivity(matches, component)) { - removed.add(applicationInfo); - mIconCache.remove(component); - data.remove(i); - } - } - } - - // Find enabled activities and add them to the adapter - // Also updates existing activities with new labels/icons - int count = matches.size(); - for (int i = 0; i < count; i++) { - final ResolveInfo info = matches.get(i); - ApplicationInfo applicationInfo = findApplicationInfoLocked( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - if (applicationInfo == null) { - add(new ApplicationInfo(context.getPackageManager(), info, mIconCache, null)); - } else { - mIconCache.remove(applicationInfo.componentName); - mIconCache.getTitleAndIcon(applicationInfo, info, null); - modified.add(applicationInfo); - } - } - } else { - // Remove all data for this package. - for (int i = data.size() - 1; i >= 0; i--) { - final ApplicationInfo applicationInfo = data.get(i); - final ComponentName component = applicationInfo.intent.getComponent(); - if (packageName.equals(component.getPackageName())) { - removed.add(applicationInfo); - mIconCache.remove(component); - data.remove(i); - } - } - } - } - - /** - * Query the package manager for MAIN/LAUNCHER activities in the supplied package. - */ - private static List findActivitiesForPackage(Context context, String packageName) { - final PackageManager packageManager = context.getPackageManager(); - - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - mainIntent.setPackage(packageName); - - final List apps = packageManager.queryIntentActivities(mainIntent, 0); - return apps != null ? apps : new ArrayList(); - } - - /** - * Returns whether apps contains component. - */ - private static boolean findActivity(List apps, ComponentName component) { - final String className = component.getClassName(); - for (ResolveInfo info : apps) { - final ActivityInfo activityInfo = info.activityInfo; - if (activityInfo.name.equals(className)) { - return true; - } - } - return false; - } - - /** - * Returns whether apps contains component. - */ - private static boolean findActivity(ArrayList apps, ComponentName component) { - final int N = apps.size(); - for (int i=0; i list); - - public void addApps(ArrayList list); - - public void removeApps(ArrayList list); - - public void updateApps(ArrayList list); - - // Resets the AllApps page to the front - public void reset(); - - public void dumpState(); - - public void surrender(); -} diff --git a/src/com/android/launcher2/AppWidgetResizeFrame.java b/src/com/android/launcher2/AppWidgetResizeFrame.java deleted file mode 100644 index 882468624..000000000 --- a/src/com/android/launcher2/AppWidgetResizeFrame.java +++ /dev/null @@ -1,429 +0,0 @@ -package com.android.launcher2; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetProviderInfo; -import android.content.Context; -import android.graphics.Rect; -import android.view.Gravity; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.launcher.R; - -public class AppWidgetResizeFrame extends FrameLayout { - private LauncherAppWidgetHostView mWidgetView; - private CellLayout mCellLayout; - private DragLayer mDragLayer; - private Workspace mWorkspace; - private ImageView mLeftHandle; - private ImageView mRightHandle; - private ImageView mTopHandle; - private ImageView mBottomHandle; - - private boolean mLeftBorderActive; - private boolean mRightBorderActive; - private boolean mTopBorderActive; - private boolean mBottomBorderActive; - - private int mWidgetPaddingLeft; - private int mWidgetPaddingRight; - private int mWidgetPaddingTop; - private int mWidgetPaddingBottom; - - private int mBaselineWidth; - private int mBaselineHeight; - private int mBaselineX; - private int mBaselineY; - private int mResizeMode; - - private int mRunningHInc; - private int mRunningVInc; - private int mMinHSpan; - private int mMinVSpan; - private int mDeltaX; - private int mDeltaY; - private int mDeltaXAddOn; - private int mDeltaYAddOn; - - private int mBackgroundPadding; - private int mTouchTargetWidth; - - int[] mDirectionVector = new int[2]; - - final int SNAP_DURATION = 150; - final int BACKGROUND_PADDING = 24; - final float DIMMED_HANDLE_ALPHA = 0f; - final float RESIZE_THRESHOLD = 0.66f; - - public static final int LEFT = 0; - public static final int TOP = 1; - public static final int RIGHT = 2; - public static final int BOTTOM = 3; - - private Launcher mLauncher; - - public AppWidgetResizeFrame(Context context, - LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { - - super(context); - mLauncher = (Launcher) context; - mCellLayout = cellLayout; - mWidgetView = widgetView; - mResizeMode = widgetView.getAppWidgetInfo().resizeMode; - mDragLayer = dragLayer; - mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); - - final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo(); - int[] result = Launcher.getMinSpanForWidget(mLauncher, info); - mMinHSpan = result[0]; - mMinVSpan = result[1]; - - setBackgroundResource(R.drawable.widget_resize_frame_holo); - setPadding(0, 0, 0, 0); - - LayoutParams lp; - mLeftHandle = new ImageView(context); - mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left); - lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, - Gravity.LEFT | Gravity.CENTER_VERTICAL); - addView(mLeftHandle, lp); - - mRightHandle = new ImageView(context); - mRightHandle.setImageResource(R.drawable.widget_resize_handle_right); - lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, - Gravity.RIGHT | Gravity.CENTER_VERTICAL); - addView(mRightHandle, lp); - - mTopHandle = new ImageView(context); - mTopHandle.setImageResource(R.drawable.widget_resize_handle_top); - lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, - Gravity.CENTER_HORIZONTAL | Gravity.TOP); - addView(mTopHandle, lp); - - mBottomHandle = new ImageView(context); - mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom); - lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, - Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - addView(mBottomHandle, lp); - - Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context, - widgetView.getAppWidgetInfo().provider, null); - mWidgetPaddingLeft = p.left; - mWidgetPaddingTop = p.top; - mWidgetPaddingRight = p.right; - mWidgetPaddingBottom = p.bottom; - - if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { - mTopHandle.setVisibility(GONE); - mBottomHandle.setVisibility(GONE); - } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { - mLeftHandle.setVisibility(GONE); - mRightHandle.setVisibility(GONE); - } - - final float density = mLauncher.getResources().getDisplayMetrics().density; - mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING); - mTouchTargetWidth = 2 * mBackgroundPadding; - - // When we create the resize frame, we first mark all cells as unoccupied. The appropriate - // cells (same if not resized, or different) will be marked as occupied when the resize - // frame is dismissed. - mCellLayout.markCellsAsUnoccupiedForView(mWidgetView); - } - - public boolean beginResizeIfPointInRegion(int x, int y) { - boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; - boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; - mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive; - mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive; - mTopBorderActive = (y < mTouchTargetWidth) && verticalActive; - mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive; - - boolean anyBordersActive = mLeftBorderActive || mRightBorderActive - || mTopBorderActive || mBottomBorderActive; - - mBaselineWidth = getMeasuredWidth(); - mBaselineHeight = getMeasuredHeight(); - mBaselineX = getLeft(); - mBaselineY = getTop(); - - if (anyBordersActive) { - mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); - mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA); - mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); - mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); - } - return anyBordersActive; - } - - /** - * Here we bound the deltas such that the frame cannot be stretched beyond the extents - * of the CellLayout, and such that the frame's borders can't cross. - */ - public void updateDeltas(int deltaX, int deltaY) { - if (mLeftBorderActive) { - mDeltaX = Math.max(-mBaselineX, deltaX); - mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX); - } else if (mRightBorderActive) { - mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX); - mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX); - } - - if (mTopBorderActive) { - mDeltaY = Math.max(-mBaselineY, deltaY); - mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY); - } else if (mBottomBorderActive) { - mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY); - mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY); - } - } - - public void visualizeResizeForDelta(int deltaX, int deltaY) { - visualizeResizeForDelta(deltaX, deltaY, false); - } - - /** - * Based on the deltas, we resize the frame, and, if needed, we resize the widget. - */ - private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) { - updateDeltas(deltaX, deltaY); - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - - if (mLeftBorderActive) { - lp.x = mBaselineX + mDeltaX; - lp.width = mBaselineWidth - mDeltaX; - } else if (mRightBorderActive) { - lp.width = mBaselineWidth + mDeltaX; - } - - if (mTopBorderActive) { - lp.y = mBaselineY + mDeltaY; - lp.height = mBaselineHeight - mDeltaY; - } else if (mBottomBorderActive) { - lp.height = mBaselineHeight + mDeltaY; - } - - resizeWidgetIfNeeded(onDismiss); - requestLayout(); - } - - /** - * Based on the current deltas, we determine if and how to resize the widget. - */ - private void resizeWidgetIfNeeded(boolean onDismiss) { - int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); - int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); - - int deltaX = mDeltaX + mDeltaXAddOn; - int deltaY = mDeltaY + mDeltaYAddOn; - - float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc; - float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc; - - int hSpanInc = 0; - int vSpanInc = 0; - int cellXInc = 0; - int cellYInc = 0; - - int countX = mCellLayout.getCountX(); - int countY = mCellLayout.getCountY(); - - if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) { - hSpanInc = Math.round(hSpanIncF); - } - if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) { - vSpanInc = Math.round(vSpanIncF); - } - - if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return; - - - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); - - int spanX = lp.cellHSpan; - int spanY = lp.cellVSpan; - int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX; - int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY; - - int hSpanDelta = 0; - int vSpanDelta = 0; - - // For each border, we bound the resizing based on the minimum width, and the maximum - // expandability. - if (mLeftBorderActive) { - cellXInc = Math.max(-cellX, hSpanInc); - cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc); - hSpanInc *= -1; - hSpanInc = Math.min(cellX, hSpanInc); - hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); - hSpanDelta = -hSpanInc; - - } else if (mRightBorderActive) { - hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc); - hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); - hSpanDelta = hSpanInc; - } - - if (mTopBorderActive) { - cellYInc = Math.max(-cellY, vSpanInc); - cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc); - vSpanInc *= -1; - vSpanInc = Math.min(cellY, vSpanInc); - vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); - vSpanDelta = -vSpanInc; - } else if (mBottomBorderActive) { - vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc); - vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); - vSpanDelta = vSpanInc; - } - - mDirectionVector[0] = 0; - mDirectionVector[1] = 0; - // Update the widget's dimensions and position according to the deltas computed above - if (mLeftBorderActive || mRightBorderActive) { - spanX += hSpanInc; - cellX += cellXInc; - mDirectionVector[0] = mLeftBorderActive ? -1 : 1; - } - - if (mTopBorderActive || mBottomBorderActive) { - spanY += vSpanInc; - cellY += cellYInc; - mDirectionVector[1] = mTopBorderActive ? -1 : 1; - } - - if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return; - - if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView, - mDirectionVector, onDismiss)) { - lp.tmpCellX = cellX; - lp.tmpCellY = cellY; - lp.cellHSpan = spanX; - lp.cellVSpan = spanY; - mRunningVInc += vSpanDelta; - mRunningHInc += hSpanDelta; - if (!onDismiss) { - updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY); - } - } - mWidgetView.requestLayout(); - } - - static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher, - int spanX, int spanY) { - Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE); - Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT); - final float density = launcher.getResources().getDisplayMetrics().density; - - // Compute landscape size - int cellWidth = landMetrics.left; - int cellHeight = landMetrics.top; - int widthGap = landMetrics.right; - int heightGap = landMetrics.bottom; - int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); - int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); - - // Compute portrait size - cellWidth = portMetrics.left; - cellHeight = portMetrics.top; - widthGap = portMetrics.right; - heightGap = portMetrics.bottom; - int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); - int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); - - widgetView.updateAppWidgetSize(null, portWidth, landHeight, landWidth, portHeight); - } - - /** - * This is the final step of the resize. Here we save the new widget size and position - * to LauncherModel and animate the resize frame. - */ - public void commitResize() { - resizeWidgetIfNeeded(true); - requestLayout(); - } - - public void onTouchUp() { - int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); - int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); - - mDeltaXAddOn = mRunningHInc * xThreshold; - mDeltaYAddOn = mRunningVInc * yThreshold; - mDeltaX = 0; - mDeltaY = 0; - - post(new Runnable() { - @Override - public void run() { - snapToWidget(true); - } - }); - } - - public void snapToWidget(boolean animate) { - final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX(); - int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY(); - - int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft - - mWidgetPaddingRight; - int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop - - mWidgetPaddingBottom; - - int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft; - int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop; - - // We need to make sure the frame stays within the bounds of the CellLayout - if (newY < 0) { - newHeight -= -newY; - newY = 0; - } - if (newY + newHeight > mDragLayer.getHeight()) { - newHeight -= newY + newHeight - mDragLayer.getHeight(); - } - - if (!animate) { - lp.width = newWidth; - lp.height = newHeight; - lp.x = newX; - lp.y = newY; - mLeftHandle.setAlpha(1.0f); - mRightHandle.setAlpha(1.0f); - mTopHandle.setAlpha(1.0f); - mBottomHandle.setAlpha(1.0f); - requestLayout(); - } else { - PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth); - PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height, - newHeight); - PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX); - PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); - ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); - ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f); - ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f); - ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f); - ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f); - oa.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - requestLayout(); - } - }); - AnimatorSet set = new AnimatorSet(); - if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { - set.playTogether(oa, topOa, bottomOa); - } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { - set.playTogether(oa, leftOa, rightOa); - } else { - set.playTogether(oa, leftOa, rightOa, topOa, bottomOa); - } - - set.setDuration(SNAP_DURATION); - set.start(); - } - } -} diff --git a/src/com/android/launcher2/ApplicationInfo.java b/src/com/android/launcher2/ApplicationInfo.java deleted file mode 100644 index 281d59c68..000000000 --- a/src/com/android/launcher2/ApplicationInfo.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Represents an app in AllAppsView. - */ -class ApplicationInfo extends ItemInfo { - private static final String TAG = "Launcher2.ApplicationInfo"; - - /** - * The application name. - */ - CharSequence title; - - /** - * The intent used to start the application. - */ - Intent intent; - - /** - * A bitmap version of the application icon. - */ - Bitmap iconBitmap; - - /** - * The time at which the app was first installed. - */ - long firstInstallTime; - - ComponentName componentName; - - static final int DOWNLOADED_FLAG = 1; - static final int UPDATED_SYSTEM_APP_FLAG = 2; - - int flags = 0; - - ApplicationInfo() { - itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; - } - - /** - * Must not hold the Context. - */ - public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache, - HashMap labelCache) { - 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 |= UPDATED_SYSTEM_APP_FLAG; - } - } - firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime; - } catch (NameNotFoundException e) { - Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName); - } - - iconCache.getTitleAndIcon(this, info, labelCache); - } - - public ApplicationInfo(ApplicationInfo info) { - super(info); - componentName = info.componentName; - title = info.title.toString(); - intent = new Intent(info.intent); - flags = info.flags; - firstInstallTime = info.firstInstallTime; - } - - /** Returns the package name that the shortcut's intent will resolve to, or an empty string if - * none exists. */ - String getPackageName() { - return super.getPackageName(intent); - } - - /** - * Creates the application intent based on a component name and various launch flags. - * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}. - * - * @param className the class name of the component representing the intent - * @param launchFlags the launch flags - */ - final void setActivity(ComponentName className, int launchFlags) { - intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(className); - intent.setFlags(launchFlags); - itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION; - } - - @Override - public String toString() { - return "ApplicationInfo(title=" + title.toString() + ")"; - } - - public static void dumpApplicationInfoList(String tag, String label, - ArrayList list) { - Log.d(tag, label + " size=" + list.size()); - for (ApplicationInfo info: list) { - Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" - + info.iconBitmap + " firstInstallTime=" - + info.firstInstallTime); - } - } - - public ShortcutInfo makeShortcut() { - return new ShortcutInfo(this); - } -} diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java deleted file mode 100644 index a50836168..000000000 --- a/src/com/android/launcher2/AppsCustomizePagedView.java +++ /dev/null @@ -1,1899 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.appwidget.AppWidgetHostView; -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.Configuration; -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.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Insets; -import android.graphics.MaskFilter; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.TableMaskFilter; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.os.Process; -import android.util.AttributeSet; -import android.util.Log; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.GridLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Toast; - -import com.android.launcher.R; -import com.android.launcher2.DropTarget.DragObject; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.lang.ref.WeakReference; - -/** - * A simple callback interface which also provides the results of the task. - */ -interface AsyncTaskCallback { - void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); -} - -/** - * The data needed to perform either of the custom AsyncTasks. - */ -class AsyncTaskPageData { - enum Type { - LoadWidgetPreviewData - } - - AsyncTaskPageData(int p, ArrayList l, ArrayList si, AsyncTaskCallback bgR, - AsyncTaskCallback postR) { - page = p; - items = l; - sourceImages = si; - generatedImages = new ArrayList(); - maxImageWidth = maxImageHeight = -1; - doInBackgroundCallback = bgR; - postExecuteCallback = postR; - } - AsyncTaskPageData(int p, ArrayList l, int cw, int ch, AsyncTaskCallback bgR, - AsyncTaskCallback postR) { - page = p; - items = l; - generatedImages = new ArrayList(); - maxImageWidth = cw; - maxImageHeight = ch; - doInBackgroundCallback = bgR; - postExecuteCallback = postR; - } - void cleanup(boolean cancelled) { - // Clean up any references to source/generated bitmaps - if (sourceImages != null) { - if (cancelled) { - for (Bitmap b : sourceImages) { - b.recycle(); - } - } - sourceImages.clear(); - } - if (generatedImages != null) { - if (cancelled) { - for (Bitmap b : generatedImages) { - b.recycle(); - } - } - generatedImages.clear(); - } - } - int page; - ArrayList items; - ArrayList sourceImages; - ArrayList generatedImages; - int maxImageWidth; - int maxImageHeight; - AsyncTaskCallback doInBackgroundCallback; - AsyncTaskCallback postExecuteCallback; -} - -/** - * A generic template for an async task used in AppsCustomize. - */ -class AppsCustomizeAsyncTask extends AsyncTask { - AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) { - page = p; - threadPriority = Process.THREAD_PRIORITY_DEFAULT; - dataType = ty; - } - @Override - protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { - if (params.length != 1) return null; - // Load each of the widget previews in the background - params[0].doInBackgroundCallback.run(this, params[0]); - return params[0]; - } - @Override - protected void onPostExecute(AsyncTaskPageData result) { - // All the widget previews are loaded, so we can just callback to inflate the page - result.postExecuteCallback.run(this, result); - } - - void setThreadPriority(int p) { - threadPriority = p; - } - void syncThreadPriority() { - Process.setThreadPriority(threadPriority); - } - - // The page that this async task is associated with - AsyncTaskPageData.Type dataType; - int page; - int threadPriority; -} - -abstract class WeakReferenceThreadLocal { - private ThreadLocal> mThreadLocal; - public WeakReferenceThreadLocal() { - mThreadLocal = new ThreadLocal>(); - } - - abstract T initialValue(); - - public void set(T t) { - mThreadLocal.set(new WeakReference(t)); - } - - public T get() { - WeakReference reference = mThreadLocal.get(); - T obj; - if (reference == null) { - obj = initialValue(); - mThreadLocal.set(new WeakReference(obj)); - return obj; - } else { - obj = reference.get(); - if (obj == null) { - obj = initialValue(); - mThreadLocal.set(new WeakReference(obj)); - } - return obj; - } - } -} - -class CanvasCache extends WeakReferenceThreadLocal { - @Override - protected Canvas initialValue() { - return new Canvas(); - } -} - -class PaintCache extends WeakReferenceThreadLocal { - @Override - protected Paint initialValue() { - return null; - } -} - -class BitmapCache extends WeakReferenceThreadLocal { - @Override - protected Bitmap initialValue() { - return null; - } -} - -class RectCache extends WeakReferenceThreadLocal { - @Override - protected Rect initialValue() { - return new Rect(); - } -} - -/** - * The Apps/Customize page that displays all the applications, widgets, and shortcuts. - */ -public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements - AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource, - PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener, - LauncherTransitionable { - static final String TAG = "AppsCustomizePagedView"; - - /** - * The different content types that this paged view can show. - */ - public enum ContentType { - Applications, - Widgets - } - - // Refs - private Launcher mLauncher; - private DragController mDragController; - private final LayoutInflater mLayoutInflater; - private final PackageManager mPackageManager; - - // Save and Restore - private int mSaveInstanceStateItemIndex = -1; - private PagedViewIcon mPressedIcon; - - // Content - private ArrayList mApps; - private ArrayList mWidgets; - - // Cling - private boolean mHasShownAllAppsCling; - private int mClingFocusedX; - private int mClingFocusedY; - - // Caching - private Canvas mCanvas; - private Drawable mDefaultWidgetBackground; - private IconCache mIconCache; - - // Dimens - private int mContentWidth; - private int mAppIconSize; - private int mMaxAppCellCountX, mMaxAppCellCountY; - private int mWidgetCountX, mWidgetCountY; - private int mWidgetWidthGap, mWidgetHeightGap; - private final float sWidgetPreviewIconPaddingPercentage = 0.25f; - private PagedViewCellLayout mWidgetSpacingLayout; - private int mNumAppsPages; - private int mNumWidgetPages; - - // Relating to the scroll and overscroll effects - Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f); - private static float CAMERA_DISTANCE = 6500; - private static float TRANSITION_SCALE_FACTOR = 0.74f; - private static float TRANSITION_PIVOT = 0.65f; - private static float TRANSITION_MAX_ROTATION = 22; - private static final boolean PERFORM_OVERSCROLL_ROTATION = true; - private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); - private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); - - // Previews & outlines - ArrayList mRunningTasks; - private static final int sPageSleepDelay = 200; - - private Runnable mInflateWidgetRunnable = null; - private Runnable mBindWidgetRunnable = null; - static final int WIDGET_NO_CLEANUP_REQUIRED = -1; - static final int WIDGET_PRELOAD_PENDING = 0; - static final int WIDGET_BOUND = 1; - static final int WIDGET_INFLATED = 2; - int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; - int mWidgetLoadingId = -1; - PendingAddWidgetInfo mCreateWidgetInfo = null; - private boolean mDraggingWidget = false; - - // Deferral of loading widget previews during launcher transitions - private boolean mInTransition; - private ArrayList mDeferredSyncWidgetPageItems = - new ArrayList(); - private ArrayList mDeferredPrepareLoadWidgetPreviewsTasks = - new ArrayList(); - - // Used for drawing shortcut previews - BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); - PaintCache mCachedShortcutPreviewPaint = new PaintCache(); - CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); - - // Used for drawing widget previews - CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); - RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); - RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); - PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); - - public AppsCustomizePagedView(Context context, AttributeSet attrs) { - super(context, attrs); - mLayoutInflater = LayoutInflater.from(context); - mPackageManager = context.getPackageManager(); - mApps = new ArrayList(); - mWidgets = new ArrayList(); - mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache(); - mCanvas = new Canvas(); - mRunningTasks = new ArrayList(); - - // Save the default widget preview background - Resources resources = context.getResources(); - mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo); - mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); - mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1); - mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1); - mWidgetWidthGap = - a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0); - mWidgetHeightGap = - a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0); - mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); - mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); - mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0); - mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0); - a.recycle(); - mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); - - // The padding on the non-matched dimension for the default widget preview icons - // (top + bottom) - mFadeInAdjacentScreens = false; - - // Unless otherwise specified this view is important for accessibility. - if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - } - - @Override - protected void init() { - super.init(); - mCenterPagesVertically = false; - - Context context = getContext(); - Resources r = context.getResources(); - setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); - } - - @Override - protected void onUnhandledTap(MotionEvent ev) { - if (LauncherApplication.isScreenLarge()) { - // Dismiss AppsCustomize if we tap - mLauncher.showWorkspace(true); - } - } - - /** Returns the item index of the center item on this page so that we can restore to this - * item index when we rotate. */ - private int getMiddleComponentIndexOnCurrentPage() { - int i = -1; - if (getPageCount() > 0) { - int currentPage = getCurrentPage(); - if (currentPage < mNumAppsPages) { - PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage); - PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout(); - int numItemsPerPage = mCellCountX * mCellCountY; - int childCount = childrenLayout.getChildCount(); - if (childCount > 0) { - i = (currentPage * numItemsPerPage) + (childCount / 2); - } - } else { - int numApps = mApps.size(); - PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage); - int numItemsPerPage = mWidgetCountX * mWidgetCountY; - int childCount = layout.getChildCount(); - if (childCount > 0) { - i = numApps + - ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2); - } - } - } - return i; - } - - /** Get the index of the item to restore to if we need to restore the current page. */ - int getSaveInstanceStateIndex() { - if (mSaveInstanceStateItemIndex == -1) { - mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage(); - } - return mSaveInstanceStateItemIndex; - } - - /** Returns the page in the current orientation which is expected to contain the specified - * item index. */ - int getPageForComponent(int index) { - if (index < 0) return 0; - - if (index < mApps.size()) { - int numItemsPerPage = mCellCountX * mCellCountY; - return (index / numItemsPerPage); - } else { - int numItemsPerPage = mWidgetCountX * mWidgetCountY; - return mNumAppsPages + ((index - mApps.size()) / numItemsPerPage); - } - } - - /** Restores the page for an item at the specified index */ - void restorePageForIndex(int index) { - if (index < 0) return; - mSaveInstanceStateItemIndex = index; - } - - private void updatePageCounts() { - mNumWidgetPages = (int) Math.ceil(mWidgets.size() / - (float) (mWidgetCountX * mWidgetCountY)); - mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); - } - - protected void onDataReady(int width, int height) { - // Note that we transpose the counts in portrait so that we get a similar layout - boolean isLandscape = getResources().getConfiguration().orientation == - Configuration.ORIENTATION_LANDSCAPE; - int maxCellCountX = Integer.MAX_VALUE; - int maxCellCountY = Integer.MAX_VALUE; - if (LauncherApplication.isScreenLarge()) { - maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() : - LauncherModel.getCellCountY()); - maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() : - LauncherModel.getCellCountX()); - } - if (mMaxAppCellCountX > -1) { - maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX); - } - if (mMaxAppCellCountY > -1) { - maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY); - } - - // Now that the data is ready, we can calculate the content width, the number of cells to - // use for each page - mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); - mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, - mPageLayoutPaddingRight, mPageLayoutPaddingBottom); - mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY); - mCellCountX = mWidgetSpacingLayout.getCellCountX(); - mCellCountY = mWidgetSpacingLayout.getCellCountY(); - updatePageCounts(); - - // Force a measure to update recalculate the gaps - int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); - int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); - mWidgetSpacingLayout.measure(widthSpec, heightSpec); - mContentWidth = mWidgetSpacingLayout.getContentWidth(); - - AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost(); - final boolean hostIsTransitioning = host.isTransitioning(); - - // Restore the page - int page = getPageForComponent(mSaveInstanceStateItemIndex); - invalidatePageData(Math.max(0, page), hostIsTransitioning); - - // Show All Apps cling if we are finished transitioning, otherwise, we will try again when - // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be - // returned while animating) - if (!hostIsTransitioning) { - post(new Runnable() { - @Override - public void run() { - showAllAppsCling(); - } - }); - } - } - - void showAllAppsCling() { - if (!mHasShownAllAppsCling && isDataReady()) { - mHasShownAllAppsCling = true; - // Calculate the position for the cling punch through - int[] offset = new int[2]; - int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY); - mLauncher.getDragLayer().getLocationInDragLayer(this, offset); - // PagedViews are centered horizontally but top aligned - pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + - offset[0]; - pos[1] += offset[1]; - mLauncher.showFirstRunAllAppsCling(pos); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - if (!isDataReady()) { - if (!mApps.isEmpty() && !mWidgets.isEmpty()) { - setDataIsReady(); - setMeasuredDimension(width, height); - onDataReady(width, height); - } - } - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - public void onPackagesUpdated() { - // TODO: this isn't ideal, but we actually need to delay here. This call is triggered - // by a broadcast receiver, and in order for it to work correctly, we need to know that - // the AppWidgetService has already received and processed the same broadcast. Since there - // is no guarantee about ordering of broadcast receipt, we just delay here. This is a - // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget - // packages are added, updated or removed. - postDelayed(new Runnable() { - public void run() { - updatePackages(); - } - }, 1500); - } - - public void updatePackages() { - // Get the list of widgets and shortcuts - mWidgets.clear(); - List widgets = - AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); - Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); - List shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0); - for (AppWidgetProviderInfo widget : widgets) { - if (widget.minWidth > 0 && widget.minHeight > 0) { - // Ensure that all widgets we show can be added on a workspace of this size - int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget); - int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget); - int minSpanX = Math.min(spanXY[0], minSpanXY[0]); - int minSpanY = Math.min(spanXY[1], minSpanXY[1]); - if (minSpanX <= LauncherModel.getCellCountX() && - minSpanY <= LauncherModel.getCellCountY()) { - mWidgets.add(widget); - } else { - Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" + - widget.minWidth + ", " + widget.minHeight + ")"); - } - } else { - Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" + - widget.minWidth + ", " + widget.minHeight + ")"); - } - } - mWidgets.addAll(shortcuts); - Collections.sort(mWidgets, - new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); - updatePageCounts(); - invalidateOnDataChange(); - } - - @Override - public void onClick(View v) { - // When we have exited all apps or are in transition, disregard clicks - if (!mLauncher.isAllAppsCustomizeOpen() || - mLauncher.getWorkspace().isSwitchingState()) return; - - if (v instanceof PagedViewIcon) { - // Animate some feedback to the click - final ApplicationInfo appInfo = (ApplicationInfo) v.getTag(); - - // Lock the drawable state to pressed until we return to Launcher - if (mPressedIcon != null) { - mPressedIcon.lockDrawableState(); - } - - // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled - // to be consistent. So re-enable the flag here, and we will re-disable it as necessary - // when Launcher resumes and we are still in AllApps. - mLauncher.updateWallpaperVisibility(true); - mLauncher.startActivitySafely(v, appInfo.intent, appInfo); - - } else if (v instanceof PagedViewWidget) { - // Let the user know that they have to long press to add a widget - Toast.makeText(getContext(), R.string.long_press_widget_to_add, - Toast.LENGTH_SHORT).show(); - - // Create a little animation to show that the widget can move - float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); - final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); - AnimatorSet bounce = new AnimatorSet(); - ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY); - tyuAnim.setDuration(125); - ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f); - tydAnim.setDuration(100); - bounce.play(tyuAnim).before(tydAnim); - bounce.setInterpolator(new AccelerateInterpolator()); - bounce.start(); - } - } - - public boolean onKey(View v, int keyCode, KeyEvent event) { - return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event); - } - - /* - * PagedViewWithDraggableItems implementation - */ - @Override - protected void determineDraggingStart(android.view.MotionEvent ev) { - // Disable dragging by pulling an app down for now. - } - - private void beginDraggingApplication(View v) { - mLauncher.getWorkspace().onDragStartedWithItem(v); - mLauncher.getWorkspace().beginDragShared(v, this); - } - - private void preloadWidget(final PendingAddWidgetInfo info) { - final AppWidgetProviderInfo pInfo = info.info; - if (pInfo.configure != null) { - return; - } - - mWidgetCleanupState = WIDGET_PRELOAD_PENDING; - mBindWidgetRunnable = new Runnable() { - @Override - public void run() { - mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); - if (AppWidgetManager.getInstance(mLauncher) - .bindAppWidgetIdIfAllowed(mWidgetLoadingId, info.componentName)) { - mWidgetCleanupState = WIDGET_BOUND; - } - } - }; - post(mBindWidgetRunnable); - - mInflateWidgetRunnable = new Runnable() { - @Override - public void run() { - AppWidgetHostView hostView = mLauncher. - getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo); - info.boundWidget = hostView; - mWidgetCleanupState = WIDGET_INFLATED; - hostView.setVisibility(INVISIBLE); - int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX, - info.spanY, info, false); - - // We want the first widget layout to be the correct size. This will be important - // for width size reporting to the AppWidgetManager. - DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], - unScaledSize[1]); - lp.x = lp.y = 0; - lp.customPosition = true; - hostView.setLayoutParams(lp); - mLauncher.getDragLayer().addView(hostView); - } - }; - post(mInflateWidgetRunnable); - } - - @Override - public void onShortPress(View v) { - // We are anticipating a long press, and we use this time to load bind and instantiate - // the widget. This will need to be cleaned up if it turns out no long press occurs. - if (mCreateWidgetInfo != null) { - // Just in case the cleanup process wasn't properly executed. This shouldn't happen. - cleanupWidgetPreloading(false); - } - mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); - preloadWidget(mCreateWidgetInfo); - } - - private void cleanupWidgetPreloading(boolean widgetWasAdded) { - if (!widgetWasAdded) { - // If the widget was not added, we may need to do further cleanup. - PendingAddWidgetInfo info = mCreateWidgetInfo; - mCreateWidgetInfo = null; - - if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) { - // We never did any preloading, so just remove pending callbacks to do so - removeCallbacks(mBindWidgetRunnable); - removeCallbacks(mInflateWidgetRunnable); - } else if (mWidgetCleanupState == WIDGET_BOUND) { - // Delete the widget id which was allocated - if (mWidgetLoadingId != -1) { - mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); - } - - // We never got around to inflating the widget, so remove the callback to do so. - removeCallbacks(mInflateWidgetRunnable); - } else if (mWidgetCleanupState == WIDGET_INFLATED) { - // Delete the widget id which was allocated - if (mWidgetLoadingId != -1) { - mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); - } - - // The widget was inflated and added to the DragLayer -- remove it. - AppWidgetHostView widget = info.boundWidget; - mLauncher.getDragLayer().removeView(widget); - } - } - mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; - mWidgetLoadingId = -1; - mCreateWidgetInfo = null; - PagedViewWidget.resetShortPressTarget(); - } - - @Override - public void cleanUpShortPress(View v) { - if (!mDraggingWidget) { - cleanupWidgetPreloading(false); - } - } - - private boolean beginDraggingWidget(View v) { - mDraggingWidget = true; - // Get the widget preview as the drag representation - ImageView image = (ImageView) v.findViewById(R.id.widget_preview); - PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); - - // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and - // we abort the drag. - if (image.getDrawable() == null) { - mDraggingWidget = false; - return false; - } - - // Compose the drag image - Bitmap preview; - Bitmap outline; - float scale = 1f; - if (createItemInfo instanceof PendingAddWidgetInfo) { - // This can happen in some weird cases involving multi-touch. We can't start dragging - // the widget if this is null, so we break out. - if (mCreateWidgetInfo == null) { - return false; - } - - PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo; - createItemInfo = createWidgetInfo; - int spanX = createItemInfo.spanX; - int spanY = createItemInfo.spanY; - int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY, - createWidgetInfo, true); - - FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable(); - float minScale = 1.25f; - int maxWidth, maxHeight; - maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]); - maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]); - preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage, - createWidgetInfo.icon, spanX, spanY, maxWidth, maxHeight); - - // Determine the image view drawable scale relative to the preview - float[] mv = new float[9]; - Matrix m = new Matrix(); - m.setRectToRect( - new RectF(0f, 0f, (float) preview.getWidth(), (float) preview.getHeight()), - new RectF(0f, 0f, (float) previewDrawable.getIntrinsicWidth(), - (float) previewDrawable.getIntrinsicHeight()), - Matrix.ScaleToFit.START); - m.getValues(mv); - scale = (float) mv[0]; - } else { - PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag(); - Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo); - preview = Bitmap.createBitmap(icon.getIntrinsicWidth(), - icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - - mCanvas.setBitmap(preview); - mCanvas.save(); - renderDrawableToBitmap(icon, preview, 0, 0, - icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); - mCanvas.restore(); - mCanvas.setBitmap(null); - createItemInfo.spanX = createItemInfo.spanY = 1; - } - - // We use a custom alpha clip table for the default widget previews - Paint alphaClipPaint = null; - if (createItemInfo instanceof PendingAddWidgetInfo) { - if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) { - MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255); - alphaClipPaint = new Paint(); - alphaClipPaint.setMaskFilter(alphaClipTable); - } - } - - // Save the preview for the outline generation, then dim the preview - outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(), - false); - - // Start the drag - alphaClipPaint = null; - mLauncher.lockScreenOrientation(); - mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, alphaClipPaint); - mDragController.startDrag(image, preview, this, createItemInfo, - DragController.DRAG_ACTION_COPY, null, scale); - outline.recycle(); - preview.recycle(); - return true; - } - - @Override - protected boolean beginDragging(final View v) { - if (!super.beginDragging(v)) return false; - - if (v instanceof PagedViewIcon) { - beginDraggingApplication(v); - } else if (v instanceof PagedViewWidget) { - if (!beginDraggingWidget(v)) { - return false; - } - } - - // We delay entering spring-loaded mode slightly to make sure the UI - // thready is free of any work. - postDelayed(new Runnable() { - @Override - public void run() { - // We don't enter spring-loaded mode if the drag has been cancelled - if (mLauncher.getDragController().isDragging()) { - // Dismiss the cling - mLauncher.dismissAllAppsCling(null); - - // Reset the alpha on the dragged icon before we drag - resetDrawableState(); - - // Go into spring loaded mode (must happen before we startDrag()) - mLauncher.enterSpringLoadedDragMode(); - } - } - }, 150); - - return true; - } - - /** - * Clean up after dragging. - * - * @param target where the item was dragged to (can be null if the item was flung) - */ - private void endDragging(View target, boolean isFlingToDelete, boolean success) { - if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && - !(target instanceof DeleteDropTarget))) { - // Exit spring loaded mode if we have not successfully dropped or have not handled the - // drop in Workspace - mLauncher.exitSpringLoadedDragMode(); - } - mLauncher.unlockScreenOrientation(false); - } - - @Override - public View getContent() { - return null; - } - - @Override - public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { - mInTransition = true; - if (toWorkspace) { - cancelAllTasks(); - } - } - - @Override - public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { - } - - @Override - public void onLauncherTransitionStep(Launcher l, float t) { - } - - @Override - public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { - mInTransition = false; - for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) { - onSyncWidgetPageItems(d); - } - mDeferredSyncWidgetPageItems.clear(); - for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) { - r.run(); - } - mDeferredPrepareLoadWidgetPreviewsTasks.clear(); - mForceDrawAllChildrenNextFrame = !toWorkspace; - } - - @Override - public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, - boolean success) { - // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling - if (isFlingToDelete) return; - - endDragging(target, false, success); - - // Display an error message if the drag failed due to there not being enough space on the - // target layout we were dropping on. - if (!success) { - boolean showOutOfSpaceMessage = false; - if (target instanceof Workspace) { - int currentScreen = mLauncher.getCurrentWorkspaceScreen(); - Workspace workspace = (Workspace) target; - CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); - ItemInfo itemInfo = (ItemInfo) d.dragInfo; - if (layout != null) { - layout.calculateSpans(itemInfo); - showOutOfSpaceMessage = - !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); - } - } - if (showOutOfSpaceMessage) { - mLauncher.showOutOfSpaceMessage(false); - } - - d.deferDragViewCleanupPostAnimation = false; - } - cleanupWidgetPreloading(success); - mDraggingWidget = false; - } - - @Override - public void onFlingToDeleteCompleted() { - // We just dismiss the drag when we fling, so cleanup here - endDragging(null, true, true); - cleanupWidgetPreloading(false); - mDraggingWidget = false; - } - - @Override - public boolean supportsFlingToDelete() { - return true; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - cancelAllTasks(); - } - - public void clearAllWidgetPages() { - cancelAllTasks(); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View v = getPageAt(i); - if (v instanceof PagedViewGridLayout) { - ((PagedViewGridLayout) v).removeAllViewsOnPage(); - mDirtyPageContent.set(i, true); - } - } - } - - private void cancelAllTasks() { - // Clean up all the async tasks - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); - task.cancel(false); - iter.remove(); - mDirtyPageContent.set(task.page, true); - - // We've already preallocated the views for the data to load into, so clear them as well - View v = getPageAt(task.page); - if (v instanceof PagedViewGridLayout) { - ((PagedViewGridLayout) v).removeAllViewsOnPage(); - } - } - mDeferredSyncWidgetPageItems.clear(); - mDeferredPrepareLoadWidgetPreviewsTasks.clear(); - } - - public void setContentType(ContentType type) { - if (type == ContentType.Widgets) { - invalidatePageData(mNumAppsPages, true); - } else if (type == ContentType.Applications) { - invalidatePageData(0, true); - } - } - - protected void snapToPage(int whichPage, int delta, int duration) { - super.snapToPage(whichPage, delta, duration); - updateCurrentTab(whichPage); - - // Update the thread priorities given the direction lookahead - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); - int pageIndex = task.page; - if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) || - (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) { - task.setThreadPriority(getThreadPriorityForPage(pageIndex)); - } else { - task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); - } - } - } - - private void updateCurrentTab(int currentPage) { - AppsCustomizeTabHost tabHost = getTabHost(); - if (tabHost != null) { - String tag = tabHost.getCurrentTabTag(); - if (tag != null) { - if (currentPage >= mNumAppsPages && - !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) { - tabHost.setCurrentTabFromContent(ContentType.Widgets); - } else if (currentPage < mNumAppsPages && - !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { - tabHost.setCurrentTabFromContent(ContentType.Applications); - } - } - } - } - - /* - * Apps PagedView implementation - */ - private void setVisibilityOnChildren(ViewGroup layout, int visibility) { - int childCount = layout.getChildCount(); - for (int i = 0; i < childCount; ++i) { - layout.getChildAt(i).setVisibility(visibility); - } - } - private void setupPage(PagedViewCellLayout layout) { - layout.setCellCount(mCellCountX, mCellCountY); - layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); - layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, - mPageLayoutPaddingRight, mPageLayoutPaddingBottom); - - // Note: We force a measure here to get around the fact that when we do layout calculations - // immediately after syncing, we don't have a proper width. That said, we already know the - // expected page width, so we can actually optimize by hiding all the TextView-based - // children that are expensive to measure, and let that happen naturally later. - setVisibilityOnChildren(layout, View.GONE); - int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); - int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); - layout.setMinimumWidth(getPageContentWidth()); - layout.measure(widthSpec, heightSpec); - setVisibilityOnChildren(layout, View.VISIBLE); - } - - public void syncAppsPageItems(int page, boolean immediate) { - // 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) getPageAt(page); - - layout.removeAllViewsOnPage(); - ArrayList items = new ArrayList(); - ArrayList images = new ArrayList(); - for (int i = startIndex; i < endIndex; ++i) { - ApplicationInfo info = mApps.get(i); - PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( - R.layout.apps_customize_application, layout, false); - icon.applyFromApplicationInfo(info, true, this); - icon.setOnClickListener(this); - icon.setOnLongClickListener(this); - icon.setOnTouchListener(this); - icon.setOnKeyListener(this); - - int index = i - startIndex; - int x = index % mCellCountX; - int y = index / mCellCountX; - layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); - - items.add(info); - images.add(info.iconBitmap); - } - - layout.createHardwareLayers(); - } - - /** - * A helper to return the priority for loading of the specified widget page. - */ - private int getWidgetPageLoadPriority(int page) { - // If we are snapping to another page, use that index as the target page index - int toPage = mCurrentPage; - if (mNextPage > -1) { - toPage = mNextPage; - } - - // We use the distance from the target page as an initial guess of priority, but if there - // are no pages of higher priority than the page specified, then bump up the priority of - // the specified page. - Iterator iter = mRunningTasks.iterator(); - int minPageDiff = Integer.MAX_VALUE; - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); - minPageDiff = Math.abs(task.page - toPage); - } - - int rawPageDiff = Math.abs(page - toPage); - return rawPageDiff - Math.min(rawPageDiff, minPageDiff); - } - /** - * Return the appropriate thread priority for loading for a given page (we give the current - * page much higher priority) - */ - private int getThreadPriorityForPage(int page) { - // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below - int pageDiff = getWidgetPageLoadPriority(page); - if (pageDiff <= 0) { - return Process.THREAD_PRIORITY_LESS_FAVORABLE; - } else if (pageDiff <= 1) { - return Process.THREAD_PRIORITY_LOWEST; - } else { - return Process.THREAD_PRIORITY_LOWEST; - } - } - private int getSleepForPage(int page) { - int pageDiff = getWidgetPageLoadPriority(page); - return Math.max(0, pageDiff * sPageSleepDelay); - } - /** - * Creates and executes a new AsyncTask to load a page of widget previews. - */ - private void prepareLoadWidgetPreviewsTask(int page, ArrayList widgets, - int cellWidth, int cellHeight, int cellCountX) { - - // Prune all tasks that are no longer needed - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); - int taskPage = task.page; - if (taskPage < getAssociatedLowerPageBound(mCurrentPage) || - taskPage > getAssociatedUpperPageBound(mCurrentPage)) { - task.cancel(false); - iter.remove(); - } else { - task.setThreadPriority(getThreadPriorityForPage(taskPage)); - } - } - - // We introduce a slight delay to order the loading of side pages so that we don't thrash - final int sleepMs = getSleepForPage(page); - AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, - new AsyncTaskCallback() { - @Override - public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { - try { - try { - Thread.sleep(sleepMs); - } catch (Exception e) {} - loadWidgetPreviewsInBackground(task, data); - } finally { - if (task.isCancelled()) { - data.cleanup(true); - } - } - } - }, - new AsyncTaskCallback() { - @Override - public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { - mRunningTasks.remove(task); - if (task.isCancelled()) return; - // do cleanup inside onSyncWidgetPageItems - onSyncWidgetPageItems(data); - } - }); - - // Ensure that the task is appropriately prioritized and runs in parallel - AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, - AsyncTaskPageData.Type.LoadWidgetPreviewData); - t.setThreadPriority(getThreadPriorityForPage(page)); - t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); - mRunningTasks.add(t); - } - - /* - * Widgets PagedView implementation - */ - private void setupPage(PagedViewGridLayout layout) { - layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, - mPageLayoutPaddingRight, mPageLayoutPaddingBottom); - - // Note: We force a measure here to get around the fact that when we do layout calculations - // immediately after syncing, we don't have a proper width. - int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); - int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); - layout.setMinimumWidth(getPageContentWidth()); - layout.measure(widthSpec, heightSpec); - } - - private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) { - renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); - } - - private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, - float scale) { - if (bitmap != null) { - Canvas c = new Canvas(bitmap); - c.scale(scale, scale); - Rect oldBounds = d.copyBounds(); - d.setBounds(x, y, x + w, y + h); - d.draw(c); - d.setBounds(oldBounds); // Restore the bounds - c.setBitmap(null); - } - } - - private Bitmap getShortcutPreview(ResolveInfo info, int maxWidth, int maxHeight) { - Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get(); - final Canvas c = mCachedShortcutPreviewCanvas.get(); - if (tempBitmap == null || - tempBitmap.getWidth() != maxWidth || - tempBitmap.getHeight() != maxHeight) { - tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); - mCachedShortcutPreviewBitmap.set(tempBitmap); - } else { - c.setBitmap(tempBitmap); - c.drawColor(0, PorterDuff.Mode.CLEAR); - c.setBitmap(null); - } - // Render the icon - Drawable icon = mIconCache.getFullResIcon(info); - - int paddingTop = - getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); - int paddingLeft = - getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left); - int paddingRight = - getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right); - - int scaledIconWidth = (maxWidth - paddingLeft - paddingRight); - float scaleSize = scaledIconWidth / (float) mAppIconSize; - - renderDrawableToBitmap( - icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth); - - Bitmap preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); - c.setBitmap(preview); - Paint p = mCachedShortcutPreviewPaint.get(); - if (p == null) { - p = new Paint(); - ColorMatrix colorMatrix = new ColorMatrix(); - colorMatrix.setSaturation(0); - p.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); - p.setAlpha((int) (255 * 0.06f)); - //float density = 1f; - //p.setMaskFilter(new BlurMaskFilter(15*density, BlurMaskFilter.Blur.NORMAL)); - mCachedShortcutPreviewPaint.set(p); - } - c.drawBitmap(tempBitmap, 0, 0, p); - c.setBitmap(null); - - renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize); - - return preview; - } - - private Bitmap getWidgetPreview(ComponentName provider, int previewImage, - int iconId, int cellHSpan, int cellVSpan, int maxWidth, - int maxHeight) { - // Load the preview image if possible - String packageName = provider.getPackageName(); - if (maxWidth < 0) maxWidth = Integer.MAX_VALUE; - if (maxHeight < 0) maxHeight = Integer.MAX_VALUE; - - Drawable drawable = null; - if (previewImage != 0) { - drawable = mPackageManager.getDrawable(packageName, previewImage, null); - if (drawable == null) { - Log.w(TAG, "Can't load widget preview drawable 0x" + - Integer.toHexString(previewImage) + " for provider: " + provider); - } - } - - int bitmapWidth; - int bitmapHeight; - Bitmap defaultPreview = null; - boolean widgetPreviewExists = (drawable != null); - if (widgetPreviewExists) { - bitmapWidth = drawable.getIntrinsicWidth(); - bitmapHeight = drawable.getIntrinsicHeight(); - } else { - // Generate a preview image if we couldn't load one - if (cellHSpan < 1) cellHSpan = 1; - if (cellVSpan < 1) cellVSpan = 1; - - BitmapDrawable previewDrawable = (BitmapDrawable) getResources() - .getDrawable(R.drawable.widget_preview_tile); - final int previewDrawableWidth = previewDrawable - .getIntrinsicWidth(); - final int previewDrawableHeight = previewDrawable - .getIntrinsicHeight(); - bitmapWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips - bitmapHeight = previewDrawableHeight * cellVSpan; - - defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, - Config.ARGB_8888); - final Canvas c = mCachedAppWidgetPreviewCanvas.get(); - c.setBitmap(defaultPreview); - previewDrawable.setBounds(0, 0, bitmapWidth, bitmapHeight); - previewDrawable.setTileModeXY(Shader.TileMode.REPEAT, - Shader.TileMode.REPEAT); - previewDrawable.draw(c); - c.setBitmap(null); - - // Draw the icon in the top left corner - int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); - int smallestSide = Math.min(bitmapWidth, bitmapHeight); - float iconScale = Math.min((float) smallestSide - / (mAppIconSize + 2 * minOffset), 1f); - - try { - Drawable icon = null; - int hoffset = - (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); - int yoffset = - (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); - if (iconId > 0) - icon = mIconCache.getFullResIcon(packageName, iconId); - Resources resources = mLauncher.getResources(); - if (icon != null) { - renderDrawableToBitmap(icon, defaultPreview, hoffset, - yoffset, (int) (mAppIconSize * iconScale), - (int) (mAppIconSize * iconScale)); - } - } catch (Resources.NotFoundException e) { - } - } - - // Scale to fit width only - let the widget preview be clipped in the - // vertical dimension - float scale = 1f; - if (bitmapWidth > maxWidth) { - scale = maxWidth / (float) bitmapWidth; - } - if (scale != 1f) { - bitmapWidth = (int) (scale * bitmapWidth); - bitmapHeight = (int) (scale * bitmapHeight); - } - - Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, - Config.ARGB_8888); - - // Draw the scaled preview into the final bitmap - if (widgetPreviewExists) { - renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, - bitmapHeight); - } else { - final Canvas c = mCachedAppWidgetPreviewCanvas.get(); - final Rect src = mCachedAppWidgetPreviewSrcRect.get(); - final Rect dest = mCachedAppWidgetPreviewDestRect.get(); - c.setBitmap(preview); - src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); - dest.set(0, 0, preview.getWidth(), preview.getHeight()); - - Paint p = mCachedAppWidgetPreviewPaint.get(); - if (p == null) { - p = new Paint(); - p.setFilterBitmap(true); - mCachedAppWidgetPreviewPaint.set(p); - } - c.drawBitmap(defaultPreview, src, dest, p); - c.setBitmap(null); - } - return preview; - } - - public void syncWidgetPageItems(final int page, final boolean immediate) { - int numItemsPerPage = mWidgetCountX * mWidgetCountY; - - // Calculate the dimensions of each cell we are giving to each widget - final ArrayList items = new ArrayList(); - int contentWidth = mWidgetSpacingLayout.getContentWidth(); - final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight - - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX); - int contentHeight = mWidgetSpacingLayout.getContentHeight(); - final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom - - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY); - - // Prepare the set of widgets to load previews for in the background - int offset = (page - mNumAppsPages) * numItemsPerPage; - for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) { - items.add(mWidgets.get(i)); - } - - // Prepopulate the pages with the other widget info, and fill in the previews later - final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); - layout.setColumnCount(layout.getCellCountX()); - for (int i = 0; i < items.size(); ++i) { - Object rawInfo = items.get(i); - PendingAddItemInfo createItemInfo = null; - PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( - R.layout.apps_customize_widget, layout, false); - if (rawInfo instanceof AppWidgetProviderInfo) { - // Fill in the widget information - AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; - createItemInfo = new PendingAddWidgetInfo(info, null, null); - - // Determine the widget spans and min resize spans. - int[] spanXY = Launcher.getSpanForWidget(mLauncher, info); - createItemInfo.spanX = spanXY[0]; - createItemInfo.spanY = spanXY[1]; - int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info); - createItemInfo.minSpanX = minSpanXY[0]; - createItemInfo.minSpanY = minSpanXY[1]; - - widget.applyFromAppWidgetProviderInfo(info, -1, spanXY); - widget.setTag(createItemInfo); - widget.setShortPressListener(this); - } else if (rawInfo instanceof ResolveInfo) { - // Fill in the shortcuts information - ResolveInfo info = (ResolveInfo) rawInfo; - createItemInfo = new PendingAddShortcutInfo(info.activityInfo); - createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; - createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, - info.activityInfo.name); - widget.applyFromResolveInfo(mPackageManager, info); - widget.setTag(createItemInfo); - } - widget.setOnClickListener(this); - widget.setOnLongClickListener(this); - widget.setOnTouchListener(this); - widget.setOnKeyListener(this); - - // Layout each widget - int ix = i % mWidgetCountX; - int iy = i / mWidgetCountX; - GridLayout.LayoutParams lp = new GridLayout.LayoutParams( - GridLayout.spec(iy, GridLayout.LEFT), - GridLayout.spec(ix, GridLayout.TOP)); - lp.width = cellWidth; - lp.height = cellHeight; - lp.setGravity(Gravity.TOP | Gravity.LEFT); - if (ix > 0) lp.leftMargin = mWidgetWidthGap; - if (iy > 0) lp.topMargin = mWidgetHeightGap; - layout.addView(widget, lp); - } - - // wait until a call on onLayout to start loading, because - // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out - // TODO: can we do a measure/layout immediately? - layout.setOnLayoutListener(new Runnable() { - public void run() { - // Load the widget previews - int maxPreviewWidth = cellWidth; - int maxPreviewHeight = cellHeight; - if (layout.getChildCount() > 0) { - PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0); - int[] maxSize = w.getPreviewSize(); - maxPreviewWidth = maxSize[0]; - maxPreviewHeight = maxSize[1]; - } - if (immediate) { - AsyncTaskPageData data = new AsyncTaskPageData(page, items, - maxPreviewWidth, maxPreviewHeight, null, null); - loadWidgetPreviewsInBackground(null, data); - onSyncWidgetPageItems(data); - } else { - if (mInTransition) { - mDeferredPrepareLoadWidgetPreviewsTasks.add(this); - } else { - prepareLoadWidgetPreviewsTask(page, items, - maxPreviewWidth, maxPreviewHeight, mWidgetCountX); - } - } - } - }); - } - private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, - AsyncTaskPageData data) { - // loadWidgetPreviewsInBackground can be called without a task to load a set of widget - // previews synchronously - if (task != null) { - // Ensure that this task starts running at the correct priority - task.syncThreadPriority(); - } - - // Load each of the widget/shortcut previews - ArrayList items = data.items; - ArrayList images = data.generatedImages; - int count = items.size(); - for (int i = 0; i < count; ++i) { - if (task != null) { - // Ensure we haven't been cancelled yet - if (task.isCancelled()) break; - // Before work on each item, ensure that this task is running at the correct - // priority - task.syncThreadPriority(); - } - - Object rawInfo = items.get(i); - if (rawInfo instanceof AppWidgetProviderInfo) { - AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; - int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info); - - int maxWidth = Math.min(data.maxImageWidth, - mWidgetSpacingLayout.estimateCellWidth(cellSpans[0])); - int maxHeight = Math.min(data.maxImageHeight, - mWidgetSpacingLayout.estimateCellHeight(cellSpans[1])); - Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon, - cellSpans[0], cellSpans[1], maxWidth, maxHeight); - images.add(b); - } else if (rawInfo instanceof ResolveInfo) { - // Fill in the shortcuts information - ResolveInfo info = (ResolveInfo) rawInfo; - images.add(getShortcutPreview(info, data.maxImageWidth, data.maxImageHeight)); - } - } - } - - private void onSyncWidgetPageItems(AsyncTaskPageData data) { - if (mInTransition) { - mDeferredSyncWidgetPageItems.add(data); - return; - } - try { - int page = data.page; - PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); - - ArrayList items = data.items; - int count = items.size(); - for (int i = 0; i < count; ++i) { - PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i); - if (widget != null) { - Bitmap preview = data.generatedImages.get(i); - widget.applyPreview(new FastBitmapDrawable(preview), i); - } - } - - layout.createHardwareLayer(); - invalidate(); - - // Update all thread priorities - Iterator iter = mRunningTasks.iterator(); - while (iter.hasNext()) { - AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); - int pageIndex = task.page; - task.setThreadPriority(getThreadPriorityForPage(pageIndex)); - } - } finally { - data.cleanup(false); - } - } - - @Override - public void syncPages() { - removeAllViews(); - cancelAllTasks(); - - Context context = getContext(); - for (int j = 0; j < mNumWidgetPages; ++j) { - PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, - mWidgetCountY); - setupPage(layout); - addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - } - - for (int i = 0; i < mNumAppsPages; ++i) { - PagedViewCellLayout layout = new PagedViewCellLayout(context); - setupPage(layout); - addView(layout); - } - } - - @Override - public void syncPageItems(int page, boolean immediate) { - if (page < mNumAppsPages) { - syncAppsPageItems(page, immediate); - } else { - syncWidgetPageItems(page, immediate); - } - } - - // We want our pages to be z-ordered such that the further a page is to the left, the higher - // it is in the z-order. This is important to insure touch events are handled correctly. - View getPageAt(int index) { - return getChildAt(indexToPage(index)); - } - - @Override - protected int indexToPage(int index) { - return getChildCount() - index - 1; - } - - // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. - @Override - protected void screenScrolled(int screenCenter) { - super.screenScrolled(screenCenter); - - for (int i = 0; i < getChildCount(); i++) { - View v = getPageAt(i); - if (v != null) { - float scrollProgress = getScrollProgress(screenCenter, v, i); - - float interpolatedProgress = - mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0))); - float scale = (1 - interpolatedProgress) + - interpolatedProgress * TRANSITION_SCALE_FACTOR; - float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth(); - - float alpha; - - if (scrollProgress < 0) { - alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( - 1 - Math.abs(scrollProgress)) : 1.0f; - } else { - // On large screens we need to fade the page as it nears its leftmost position - alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress); - } - - v.setCameraDistance(mDensity * CAMERA_DISTANCE); - int pageWidth = v.getMeasuredWidth(); - int pageHeight = v.getMeasuredHeight(); - - if (PERFORM_OVERSCROLL_ROTATION) { - if (i == 0 && scrollProgress < 0) { - // Overscroll to the left - v.setPivotX(TRANSITION_PIVOT * pageWidth); - v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); - scale = 1.0f; - alpha = 1.0f; - // On the first page, we don't want the page to have any lateral motion - translationX = 0; - } else if (i == getChildCount() - 1 && scrollProgress > 0) { - // Overscroll to the right - v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); - v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); - scale = 1.0f; - alpha = 1.0f; - // On the last page, we don't want the page to have any lateral motion. - translationX = 0; - } else { - v.setPivotY(pageHeight / 2.0f); - v.setPivotX(pageWidth / 2.0f); - v.setRotationY(0f); - } - } - - v.setTranslationX(translationX); - v.setScaleX(scale); - v.setScaleY(scale); - v.setAlpha(alpha); - - // If the view has 0 alpha, we set it to be invisible so as to prevent - // it from accepting touches - if (alpha == 0) { - v.setVisibility(INVISIBLE); - } else if (v.getVisibility() != VISIBLE) { - v.setVisibility(VISIBLE); - } - } - } - } - - protected void overScroll(float amount) { - acceleratedOverScroll(amount); - } - - /** - * Used by the parent to get the content width to set the tab bar to - * @return - */ - public int getPageContentWidth() { - return mContentWidth; - } - - @Override - protected void onPageEndMoving() { - super.onPageEndMoving(); - mForceDrawAllChildrenNextFrame = true; - // We reset the save index when we change pages so that it will be recalculated on next - // rotation - mSaveInstanceStateItemIndex = -1; - } - - /* - * AllAppsView implementation - */ - @Override - public void setup(Launcher launcher, DragController dragController) { - mLauncher = launcher; - mDragController = dragController; - } - @Override - public void zoom(float zoom, boolean animate) { - // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() - } - @Override - public boolean isVisible() { - return (getVisibility() == VISIBLE); - } - @Override - public boolean isAnimating() { - return false; - } - - /** - * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can - * appropriately determine when to invalidate the PagedView page data. In cases where the data - * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the - * next onMeasure() pass, which will trigger an invalidatePageData() itself. - */ - private void invalidateOnDataChange() { - if (!isDataReady()) { - // The next layout pass will trigger data-ready if both widgets and apps are set, so - // request a layout to trigger the page data when ready. - requestLayout(); - } else { - cancelAllTasks(); - invalidatePageData(); - } - } - - @Override - public void setApps(ArrayList list) { - mApps = list; - Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); - updatePageCounts(); - invalidateOnDataChange(); - } - private void addAppsWithoutInvalidate(ArrayList list) { - // We add it in place, in alphabetical order - int count = list.size(); - for (int i = 0; i < count; ++i) { - ApplicationInfo info = list.get(i); - int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); - if (index < 0) { - mApps.add(-(index + 1), info); - } - } - } - @Override - public void addApps(ArrayList list) { - addAppsWithoutInvalidate(list); - updatePageCounts(); - invalidateOnDataChange(); - } - private int findAppByComponent(List list, ApplicationInfo item) { - ComponentName removeComponent = item.intent.getComponent(); - 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; - } - private void removeAppsWithoutInvalidate(ArrayList list) { - // loop through all the apps and remove apps that have the same component - int length = list.size(); - for (int i = 0; i < length; ++i) { - ApplicationInfo info = list.get(i); - int removeIndex = findAppByComponent(mApps, info); - if (removeIndex > -1) { - mApps.remove(removeIndex); - } - } - } - @Override - public void removeApps(ArrayList list) { - removeAppsWithoutInvalidate(list); - updatePageCounts(); - invalidateOnDataChange(); - } - @Override - public void updateApps(ArrayList 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); - updatePageCounts(); - invalidateOnDataChange(); - } - - @Override - public void reset() { - // If we have reset, then we should not continue to restore the previous state - mSaveInstanceStateItemIndex = -1; - - AppsCustomizeTabHost tabHost = getTabHost(); - String tag = tabHost.getCurrentTabTag(); - if (tag != null) { - if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { - tabHost.setCurrentTabFromContent(ContentType.Applications); - } - } - - if (mCurrentPage != 0) { - invalidatePageData(0); - } - } - - private AppsCustomizeTabHost getTabHost() { - return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane); - } - - @Override - public void dumpState() { - // TODO: Dump information related to current list of Applications, Widgets, etc. - ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps); - dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets); - } - - private void dumpAppWidgetProviderInfoList(String tag, String label, - ArrayList list) { - Log.d(tag, label + " size=" + list.size()); - for (Object i: list) { - if (i instanceof AppWidgetProviderInfo) { - AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; - Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage - + " resizeMode=" + info.resizeMode + " configure=" + info.configure - + " initialLayout=" + info.initialLayout - + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); - } else if (i instanceof ResolveInfo) { - ResolveInfo info = (ResolveInfo) i; - Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" - + info.icon); - } - } - } - - @Override - public void surrender() { - // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we - // should stop this now. - - // Stop all background tasks - cancelAllTasks(); - } - - @Override - public void iconPressed(PagedViewIcon icon) { - // Reset the previously pressed icon and store a reference to the pressed icon so that - // we can reset it on return to Launcher (in Launcher.onResume()) - if (mPressedIcon != null) { - mPressedIcon.resetDrawableState(); - } - mPressedIcon = icon; - } - - public void resetDrawableState() { - if (mPressedIcon != null) { - mPressedIcon.resetDrawableState(); - mPressedIcon = null; - } - } - - /* - * We load an extra page on each side to prevent flashes from scrolling and loading of the - * widget previews in the background with the AsyncTasks. - */ - final static int sLookBehindPageCount = 2; - final static int sLookAheadPageCount = 2; - protected int getAssociatedLowerPageBound(int page) { - final int count = getChildCount(); - int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); - int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0); - return windowMinIndex; - } - protected int getAssociatedUpperPageBound(int page) { - final int count = getChildCount(); - int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); - int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1), - count - 1); - return windowMaxIndex; - } - - @Override - protected String getCurrentPageDescription() { - int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; - int stringId = R.string.default_scroll_format; - int count = 0; - - if (page < mNumAppsPages) { - stringId = R.string.apps_customize_apps_scroll_format; - count = mNumAppsPages; - } else { - page -= mNumAppsPages; - stringId = R.string.apps_customize_widgets_scroll_format; - count = mNumWidgetPages; - } - - return String.format(getContext().getString(stringId), page + 1, count); - } -} diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java deleted file mode 100644 index 9fa2f3237..000000000 --- a/src/com/android/launcher2/AppsCustomizeTabHost.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TabHost; -import android.widget.TabWidget; -import android.widget.TextView; - -import com.android.launcher.R; - -import java.util.ArrayList; - -public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable, - TabHost.OnTabChangeListener { - static final String LOG_TAG = "AppsCustomizeTabHost"; - - private static final String APPS_TAB_TAG = "APPS"; - private static final String WIDGETS_TAB_TAG = "WIDGETS"; - - private final LayoutInflater mLayoutInflater; - private ViewGroup mTabs; - private ViewGroup mTabsContainer; - private AppsCustomizePagedView mAppsCustomizePane; - private boolean mSuppressContentCallback = false; - private FrameLayout mAnimationBuffer; - private LinearLayout mContent; - - private boolean mInTransition; - private boolean mTransitioningToWorkspace; - private boolean mResetAfterTransition; - private Runnable mRelayoutAndMakeVisible; - - private Launcher mLauncher; - - public AppsCustomizeTabHost(Context context, AttributeSet attrs) { - super(context, attrs); - mLayoutInflater = LayoutInflater.from(context); - mRelayoutAndMakeVisible = new Runnable() { - public void run() { - mTabs.requestLayout(); - mTabsContainer.setAlpha(1f); - } - }; - } - - public void setup(Launcher launcher) { - mLauncher = launcher; - } - - /** - * Convenience methods to select specific tabs. We want to set the content type immediately - * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view - * reflects the new content (but doesn't do the animation and logic associated with changing - * tabs manually). - */ - private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { - onTabChangedStart(); - onTabChangedEnd(type); - } - void selectAppsTab() { - setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); - setCurrentTabByTag(APPS_TAB_TAG); - } - void selectWidgetsTab() { - setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); - setCurrentTabByTag(WIDGETS_TAB_TAG); - } - - /** - * Setup the tab host and create all necessary tabs. - */ - @Override - protected void onFinishInflate() { - // Setup the tab host - setup(); - - final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); - final TabWidget tabs = getTabWidget(); - final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) - findViewById(R.id.apps_customize_pane_content); - mTabs = tabs; - mTabsContainer = tabsContainer; - mAppsCustomizePane = appsCustomizePane; - mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer); - mContent = (LinearLayout) findViewById(R.id.apps_customize_content); - if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); - - // Configure the tabs content factory to return the same paged view (that we change the - // content filter on) - TabContentFactory contentFactory = new TabContentFactory() { - public View createTabContent(String tag) { - return appsCustomizePane; - } - }; - - // Create the tabs - TextView tabView; - String label; - label = getContext().getString(R.string.all_apps_button_label); - tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); - tabView.setText(label); - tabView.setContentDescription(label); - addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); - label = getContext().getString(R.string.widgets_tab_label); - tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); - tabView.setText(label); - tabView.setContentDescription(label); - addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); - setOnTabChangedListener(this); - - // Setup the key listener to jump between the last tab view and the market icon - AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); - View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); - lastTab.setOnKeyListener(keyListener); - View shopButton = findViewById(R.id.market_button); - shopButton.setOnKeyListener(keyListener); - - // Hide the tab bar until we measure - mTabsContainer.setAlpha(0f); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // Set the width of the tab list to the content width - if (remeasureTabWidth) { - int contentWidth = mAppsCustomizePane.getPageContentWidth(); - if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { - // Set the width and show the tab bar - mTabs.getLayoutParams().width = contentWidth; - post(mRelayoutAndMakeVisible); - } - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - public boolean onInterceptTouchEvent(MotionEvent ev) { - // If we are mid transitioning to the workspace, then intercept touch events here so we - // can ignore them, otherwise we just let all apps handle the touch events. - if (mInTransition && mTransitioningToWorkspace) { - return true; - } - return super.onInterceptTouchEvent(ev); - }; - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Allow touch events to fall through to the workspace if we are transitioning there - if (mInTransition && mTransitioningToWorkspace) { - return super.onTouchEvent(event); - } - - // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall - // through to the workspace and trigger showWorkspace() - if (event.getY() < mAppsCustomizePane.getBottom()) { - return true; - } - return super.onTouchEvent(event); - } - - private void onTabChangedStart() { - mAppsCustomizePane.hideScrollingIndicator(false); - } - - private void reloadCurrentPage() { - if (!LauncherApplication.isScreenLarge()) { - mAppsCustomizePane.flashScrollingIndicator(true); - } - mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); - mAppsCustomizePane.requestFocus(); - } - - private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { - mAppsCustomizePane.setContentType(type); - } - - @Override - public void onTabChanged(String tabId) { - final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); - if (mSuppressContentCallback) { - mSuppressContentCallback = false; - return; - } - - // Animate the changing of the tab content by fading pages in and out - final Resources res = getResources(); - final int duration = res.getInteger(R.integer.config_tabTransitionDuration); - - // We post a runnable here because there is a delay while the first page is loading and - // the feedback from having changed the tab almost feels better than having it stick - post(new Runnable() { - @Override - public void run() { - if (mAppsCustomizePane.getMeasuredWidth() <= 0 || - mAppsCustomizePane.getMeasuredHeight() <= 0) { - reloadCurrentPage(); - return; - } - - // Take the visible pages and re-parent them temporarily to mAnimatorBuffer - // and then cross fade to the new pages - int[] visiblePageRange = new int[2]; - mAppsCustomizePane.getVisiblePages(visiblePageRange); - if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { - // If we can't get the visible page ranges, then just skip the animation - reloadCurrentPage(); - return; - } - ArrayList visiblePages = new ArrayList(); - for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { - visiblePages.add(mAppsCustomizePane.getPageAt(i)); - } - - // We want the pages to be rendered in exactly the same way as they were when - // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer - // to be exactly the same as mAppsCustomizePane, and below, set the left/top - // parameters to be correct for each of the pages - mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); - - // mAppsCustomizePane renders its children in reverse order, so - // add the pages to mAnimationBuffer in reverse order to match that behavior - for (int i = visiblePages.size() - 1; i >= 0; i--) { - View child = visiblePages.get(i); - if (child instanceof PagedViewCellLayout) { - ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); - } else if (child instanceof PagedViewGridLayout) { - ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); - } - PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); - mAppsCustomizePane.removeView(child); - PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); - mAnimationBuffer.setAlpha(1f); - mAnimationBuffer.setVisibility(View.VISIBLE); - LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), - child.getMeasuredHeight()); - p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0); - mAnimationBuffer.addView(child, p); - } - - // Toggle the new content - onTabChangedStart(); - onTabChangedEnd(type); - - // Animate the transition - ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f); - outAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAnimationBuffer.setVisibility(View.GONE); - mAnimationBuffer.removeAllViews(); - } - @Override - public void onAnimationCancel(Animator animation) { - mAnimationBuffer.setVisibility(View.GONE); - mAnimationBuffer.removeAllViews(); - } - }); - ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f); - inAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - reloadCurrentPage(); - } - }); - AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(outAnim, inAnim); - animSet.setDuration(duration); - animSet.start(); - } - }); - } - - public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { - mSuppressContentCallback = true; - setCurrentTabByTag(getTabTagForContentType(type)); - } - - /** - * Returns the content type for the specified tab tag. - */ - public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { - if (tag.equals(APPS_TAB_TAG)) { - return AppsCustomizePagedView.ContentType.Applications; - } else if (tag.equals(WIDGETS_TAB_TAG)) { - return AppsCustomizePagedView.ContentType.Widgets; - } - return AppsCustomizePagedView.ContentType.Applications; - } - - /** - * Returns the tab tag for a given content type. - */ - public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { - if (type == AppsCustomizePagedView.ContentType.Applications) { - return APPS_TAB_TAG; - } else if (type == AppsCustomizePagedView.ContentType.Widgets) { - return WIDGETS_TAB_TAG; - } - return APPS_TAB_TAG; - } - - /** - * Disable focus on anything under this view in the hierarchy if we are not visible. - */ - @Override - public int getDescendantFocusability() { - if (getVisibility() != View.VISIBLE) { - return ViewGroup.FOCUS_BLOCK_DESCENDANTS; - } - return super.getDescendantFocusability(); - } - - void reset() { - if (mInTransition) { - // Defer to after the transition to reset - mResetAfterTransition = true; - } else { - // Reset immediately - mAppsCustomizePane.reset(); - } - } - - private void enableAndBuildHardwareLayer() { - // isHardwareAccelerated() checks if we're attached to a window and if that - // window is HW accelerated-- we were sometimes not attached to a window - // and buildLayer was throwing an IllegalStateException - if (isHardwareAccelerated()) { - // Turn on hardware layers for performance - setLayerType(LAYER_TYPE_HARDWARE, null); - - // force building the layer, so you don't get a blip early in an animation - // when the layer is created layer - buildLayer(); - - // Let the GC system know that now is a good time to do any garbage - // collection; makes it less likely we'll get a GC during the all apps - // to workspace animation - System.gc(); - } - } - - @Override - public View getContent() { - return mContent; - } - - /* LauncherTransitionable overrides */ - @Override - public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { - mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace); - mInTransition = true; - mTransitioningToWorkspace = toWorkspace; - - if (toWorkspace) { - // Going from All Apps -> Workspace - setVisibilityOfSiblingsWithLowerZOrder(VISIBLE); - // Stop the scrolling indicator - we don't want All Apps to be invalidating itself - // during the transition, especially since it has a hardware layer set on it - mAppsCustomizePane.cancelScrollingIndicatorAnimations(); - } else { - // Going from Workspace -> All Apps - mContent.setVisibility(VISIBLE); - - // Make sure the current page is loaded (we start loading the side pages after the - // transition to prevent slowing down the animation) - mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); - - if (!LauncherApplication.isScreenLarge()) { - mAppsCustomizePane.showScrollingIndicator(true); - } - } - - if (mResetAfterTransition) { - mAppsCustomizePane.reset(); - mResetAfterTransition = false; - } - } - - @Override - public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { - if (animated) { - enableAndBuildHardwareLayer(); - } - } - - @Override - public void onLauncherTransitionStep(Launcher l, float t) { - // Do nothing - } - - @Override - public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { - mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace); - mInTransition = false; - if (animated) { - setLayerType(LAYER_TYPE_NONE, null); - } - - if (!toWorkspace) { - // Going from Workspace -> All Apps - setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); - - // Dismiss the workspace cling and show the all apps cling (if not already shown) - l.dismissWorkspaceCling(null); - mAppsCustomizePane.showAllAppsCling(); - // Make sure adjacent pages are loaded (we wait until after the transition to - // prevent slowing down the animation) - mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); - - if (!LauncherApplication.isScreenLarge()) { - mAppsCustomizePane.hideScrollingIndicator(false); - } - } - } - - private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) { - ViewGroup parent = (ViewGroup) getParent(); - if (parent == null) return; - - final int count = parent.getChildCount(); - if (!isChildrenDrawingOrderEnabled()) { - for (int i = 0; i < count; i++) { - final View child = parent.getChildAt(i); - if (child == this) { - break; - } else { - if (child.getVisibility() == GONE) { - continue; - } - child.setVisibility(visibility); - } - } - } else { - throw new RuntimeException("Failed; can't get z-order of views"); - } - } - - public void onWindowVisible() { - if (getVisibility() == VISIBLE) { - mContent.setVisibility(VISIBLE); - // We unload the widget previews when the UI is hidden, so need to reload pages - // Load the current page synchronously, and the neighboring pages asynchronously - mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); - mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); - } - } - - public void onTrimMemory() { - mContent.setVisibility(GONE); - // Clear the widget pages of all their subviews - this will trigger the widget previews - // to delete their bitmaps - mAppsCustomizePane.clearAllWidgetPages(); - } - - boolean isTransitioning() { - return mInTransition; - } -} diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java deleted file mode 100644 index ddc4b9fa0..000000000 --- a/src/com/android/launcher2/BubbleTextView.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.Region.Op; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.TextView; - -/** - * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan - * because we want to make the bubble taller than the text and TextView's clip is - * too aggressive. - */ -public class BubbleTextView extends TextView { - static final float CORNER_RADIUS = 4.0f; - static final float SHADOW_LARGE_RADIUS = 4.0f; - static final float SHADOW_SMALL_RADIUS = 1.75f; - static final float SHADOW_Y_OFFSET = 2.0f; - static final int SHADOW_LARGE_COLOUR = 0xDD000000; - static final int SHADOW_SMALL_COLOUR = 0xCC000000; - static final float PADDING_H = 8.0f; - static final float PADDING_V = 3.0f; - - private int mPrevAlpha = -1; - - private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper(); - private final Canvas mTempCanvas = new Canvas(); - private final Rect mTempRect = new Rect(); - private boolean mDidInvalidateForPressedState; - private Bitmap mPressedOrFocusedBackground; - private int mFocusedOutlineColor; - private int mFocusedGlowColor; - private int mPressedOutlineColor; - private int mPressedGlowColor; - - private boolean mBackgroundSizeChanged; - private Drawable mBackground; - - private boolean mStayPressed; - private CheckLongPressHelper mLongPressHelper; - - public BubbleTextView(Context context) { - super(context); - init(); - } - - public BubbleTextView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - private void init() { - mLongPressHelper = new CheckLongPressHelper(this); - mBackground = getBackground(); - - final Resources res = getContext().getResources(); - mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor = - res.getColor(android.R.color.holo_blue_light); - - setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR); - } - - public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) { - Bitmap b = info.getIcon(iconCache); - - setCompoundDrawablesWithIntrinsicBounds(null, - new FastBitmapDrawable(b), - null, null); - setText(info.title); - setTag(info); - } - - @Override - protected boolean setFrame(int left, int top, int right, int bottom) { - if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) { - mBackgroundSizeChanged = true; - } - return super.setFrame(left, top, right, bottom); - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return who == mBackground || super.verifyDrawable(who); - } - - @Override - protected void drawableStateChanged() { - if (isPressed()) { - // In this case, we have already created the pressed outline on ACTION_DOWN, - // so we just need to do an invalidate to trigger draw - if (!mDidInvalidateForPressedState) { - setCellLayoutPressedOrFocusedIcon(); - } - } else { - // Otherwise, either clear the pressed/focused background, or create a background - // for the focused state - final boolean backgroundEmptyBefore = mPressedOrFocusedBackground == null; - if (!mStayPressed) { - mPressedOrFocusedBackground = null; - } - if (isFocused()) { - if (getLayout() == null) { - // In some cases, we get focus before we have been layed out. Set the - // background to null so that it will get created when the view is drawn. - mPressedOrFocusedBackground = null; - } else { - mPressedOrFocusedBackground = createGlowingOutline( - mTempCanvas, mFocusedGlowColor, mFocusedOutlineColor); - } - mStayPressed = false; - setCellLayoutPressedOrFocusedIcon(); - } - final boolean backgroundEmptyNow = mPressedOrFocusedBackground == null; - if (!backgroundEmptyBefore && backgroundEmptyNow) { - setCellLayoutPressedOrFocusedIcon(); - } - } - - Drawable d = mBackground; - if (d != null && d.isStateful()) { - d.setState(getDrawableState()); - } - super.drawableStateChanged(); - } - - /** - * Draw this BubbleTextView into the given Canvas. - * - * @param destCanvas the canvas to draw on - * @param padding the horizontal and vertical padding to use when drawing - */ - private void drawWithPadding(Canvas destCanvas, int padding) { - final Rect clipRect = mTempRect; - getDrawingRect(clipRect); - - // adjust the clip rect so that we don't include the text label - clipRect.bottom = - getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + getLayout().getLineTop(0); - - // 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.scale(getScaleX(), getScaleY(), - (getWidth() + padding) / 2, (getHeight() + padding) / 2); - destCanvas.translate(-getScrollX() + padding / 2, -getScrollY() + padding / 2); - destCanvas.clipRect(clipRect, Op.REPLACE); - 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 createGlowingOutline(Canvas canvas, int outlineColor, int glowColor) { - final int padding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; - final Bitmap b = Bitmap.createBitmap( - getWidth() + padding, getHeight() + padding, Bitmap.Config.ARGB_8888); - - canvas.setBitmap(b); - drawWithPadding(canvas, padding); - mOutlineHelper.applyExtraThickExpensiveOutlineWithBlur(b, canvas, glowColor, outlineColor); - canvas.setBitmap(null); - - return b; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Call the superclass onTouchEvent first, because sometimes it changes the state to - // isPressed() on an ACTION_UP - boolean result = super.onTouchEvent(event); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - // So that the pressed outline is visible immediately when isPressed() is true, - // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time - // to create it) - if (mPressedOrFocusedBackground == null) { - mPressedOrFocusedBackground = createGlowingOutline( - mTempCanvas, mPressedGlowColor, mPressedOutlineColor); - } - // Invalidate so the pressed state is visible, or set a flag so we know that we - // have to call invalidate as soon as the state is "pressed" - if (isPressed()) { - mDidInvalidateForPressedState = true; - setCellLayoutPressedOrFocusedIcon(); - } else { - mDidInvalidateForPressedState = false; - } - - mLongPressHelper.postCheckForLongPress(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - // If we've touched down and up on an item, and it's still not "pressed", then - // destroy the pressed outline - if (!isPressed()) { - mPressedOrFocusedBackground = null; - } - - mLongPressHelper.cancelLongPress(); - break; - } - return result; - } - - void setStayPressed(boolean stayPressed) { - mStayPressed = stayPressed; - if (!stayPressed) { - mPressedOrFocusedBackground = null; - } - setCellLayoutPressedOrFocusedIcon(); - } - - void setCellLayoutPressedOrFocusedIcon() { - if (getParent() instanceof ShortcutAndWidgetContainer) { - ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent(); - if (parent != null) { - CellLayout layout = (CellLayout) parent.getParent(); - layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null); - } - } - } - - void clearPressedOrFocusedBackground() { - mPressedOrFocusedBackground = null; - setCellLayoutPressedOrFocusedIcon(); - } - - Bitmap getPressedOrFocusedBackground() { - return mPressedOrFocusedBackground; - } - - int getPressedOrFocusedBackgroundPadding() { - return HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2; - } - - @Override - public void draw(Canvas canvas) { - final Drawable background = mBackground; - if (background != null) { - final int scrollX = getScrollX(); - final int scrollY = getScrollY(); - - if (mBackgroundSizeChanged) { - background.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()); - mBackgroundSizeChanged = false; - } - - if ((scrollX | scrollY) == 0) { - background.draw(canvas); - } else { - canvas.translate(scrollX, scrollY); - background.draw(canvas); - canvas.translate(-scrollX, -scrollY); - } - } - - // If text is transparent, don't draw any shadow - if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) { - getPaint().clearShadowLayer(); - super.draw(canvas); - return; - } - - // We enhance the shadow by drawing the shadow twice - getPaint().setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR); - super.draw(canvas); - canvas.save(Canvas.CLIP_SAVE_FLAG); - canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), - getScrollX() + getWidth(), - getScrollY() + getHeight(), Region.Op.INTERSECT); - getPaint().setShadowLayer(SHADOW_SMALL_RADIUS, 0.0f, 0.0f, SHADOW_SMALL_COLOUR); - super.draw(canvas); - canvas.restore(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mBackground != null) mBackground.setCallback(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mBackground != null) mBackground.setCallback(null); - } - - @Override - protected boolean onSetAlpha(int alpha) { - if (mPrevAlpha != alpha) { - mPrevAlpha = alpha; - super.onSetAlpha(alpha); - } - return true; - } - - @Override - public void cancelLongPress() { - super.cancelLongPress(); - - mLongPressHelper.cancelLongPress(); - } -} diff --git a/src/com/android/launcher2/ButtonDropTarget.java b/src/com/android/launcher2/ButtonDropTarget.java deleted file mode 100644 index 1c9fa5f95..000000000 --- a/src/com/android/launcher2/ButtonDropTarget.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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.Resources; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.launcher.R; - - -/** - * Implements a DropTarget. - */ -public class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener { - - protected final int mTransitionDuration; - - protected Launcher mLauncher; - private int mBottomDragPadding; - protected TextView mText; - protected SearchDropTargetBar mSearchDropTargetBar; - - /** Whether this drop target is active for the current drag */ - protected boolean mActive; - - /** The paint applied to the drag view on hover */ - protected int mHoverColor = 0; - - public ButtonDropTarget(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - Resources r = getResources(); - mTransitionDuration = r.getInteger(R.integer.config_dropTargetBgTransitionDuration); - mBottomDragPadding = r.getDimensionPixelSize(R.dimen.drop_target_drag_padding); - } - - void setLauncher(Launcher launcher) { - mLauncher = launcher; - } - - public boolean acceptDrop(DragObject d) { - return false; - } - - public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) { - mSearchDropTargetBar = searchDropTargetBar; - } - - protected Drawable getCurrentDrawable() { - Drawable[] drawables = getCompoundDrawables(); - for (int i = 0; i < drawables.length; ++i) { - if (drawables[i] != null) { - return drawables[i]; - } - } - return null; - } - - public void onDrop(DragObject d) { - } - - public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { - // Do nothing - } - - public void onDragEnter(DragObject d) { - d.dragView.setColor(mHoverColor); - } - - public void onDragOver(DragObject d) { - // Do nothing - } - - public void onDragExit(DragObject d) { - d.dragView.setColor(0); - } - - public void onDragStart(DragSource source, Object info, int dragAction) { - // Do nothing - } - - public boolean isDropEnabled() { - return mActive; - } - - public void onDragEnd() { - // Do nothing - } - - @Override - public void getHitRect(android.graphics.Rect outRect) { - super.getHitRect(outRect); - outRect.bottom += mBottomDragPadding; - } - - Rect getIconRect(int itemWidth, int itemHeight, int drawableWidth, int drawableHeight) { - DragLayer dragLayer = mLauncher.getDragLayer(); - - // Find the rect to animate to (the view is center aligned) - Rect to = new Rect(); - dragLayer.getViewRectRelativeToSelf(this, to); - int width = drawableWidth; - int height = drawableHeight; - int left = to.left + getPaddingLeft(); - int top = to.top + (getMeasuredHeight() - height) / 2; - to.set(left, top, left + width, top + height); - - // Center the destination rect about the trash icon - int xOffset = (int) -(itemWidth - width) / 2; - int yOffset = (int) -(itemHeight - height) / 2; - to.offset(xOffset, yOffset); - - return to; - } - - @Override - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - - public void getLocationInDragLayer(int[] loc) { - mLauncher.getDragLayer().getLocationInDragLayer(this, loc); - } -} diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java deleted file mode 100644 index c028ff1dc..000000000 --- a/src/com/android/launcher2/CellLayout.java +++ /dev/null @@ -1,3022 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewDebug; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LayoutAnimationController; - -import com.android.launcher.R; -import com.android.launcher2.FolderIcon.FolderRingAnimator; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Stack; - -public class CellLayout extends ViewGroup { - static final String TAG = "CellLayout"; - - private Launcher mLauncher; - private int mCellWidth; - private int mCellHeight; - - private int mCountX; - private int mCountY; - - private int mOriginalWidthGap; - private int mOriginalHeightGap; - private int mWidthGap; - private int mHeightGap; - private int mMaxGap; - private boolean mScrollingTransformsDirty = false; - - private final Rect mRect = new Rect(); - private final CellInfo mCellInfo = new CellInfo(); - - // 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[] mTmpXY = new int[2]; - private final int[] mTmpPoint = new int[2]; - int[] mTempLocation = new int[2]; - - boolean[][] mOccupied; - boolean[][] mTmpOccupied; - private boolean mLastDownOnOccupiedCell = false; - - private OnTouchListener mInterceptTouchListener; - - private ArrayList mFolderOuterRings = new ArrayList(); - private int[] mFolderLeaveBehindCell = {-1, -1}; - - private int mForegroundAlpha = 0; - private float mBackgroundAlpha; - private float mBackgroundAlphaMultiplier = 1.0f; - - private Drawable mNormalBackground; - private Drawable mActiveGlowBackground; - private Drawable mOverScrollForegroundDrawable; - private Drawable mOverScrollLeft; - private Drawable mOverScrollRight; - private Rect mBackgroundRect; - private Rect mForegroundRect; - private int mForegroundPadding; - - // If we're actively dragging something over this screen, mIsDragOverlapping is true - private boolean mIsDragOverlapping = false; - private final Point mDragCenter = new Point(); - - // These arrays are used to implement the drag visualization on x-large screens. - // They are used as circular arrays, indexed by mDragOutlineCurrent. - private Rect[] mDragOutlines = new Rect[4]; - 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 BubbleTextView mPressedOrFocusedIcon; - - private HashMap mReorderAnimators = new - HashMap(); - private HashMap - mShakeAnimators = new HashMap(); - - private boolean mItemPlacementDirty = false; - - // When a drag operation is in progress, holds the nearest cell to the touch point - private final int[] mDragCell = new int[2]; - - private boolean mDragging = false; - - private TimeInterpolator mEaseOutInterpolator; - private ShortcutAndWidgetContainer mShortcutsAndWidgets; - - private boolean mIsHotseat = false; - - public static final int MODE_DRAG_OVER = 0; - public static final int MODE_ON_DROP = 1; - public static final int MODE_ON_DROP_EXTERNAL = 2; - public static final int MODE_ACCEPT_DROP = 3; - private static final boolean DESTRUCTIVE_REORDER = false; - private static final boolean DEBUG_VISUALIZE_OCCUPIED = false; - - static final int LANDSCAPE = 0; - static final int PORTRAIT = 1; - - private static final float REORDER_HINT_MAGNITUDE = 0.12f; - private static final int REORDER_ANIMATION_DURATION = 150; - private float mReorderHintAnimationMagnitude; - - private ArrayList mIntersectingViews = new ArrayList(); - private Rect mOccupiedRect = new Rect(); - private int[] mDirectionVector = new int[2]; - int[] mPreviousReorderDirection = new int[2]; - private static final int INVALID_DIRECTION = -100; - private DropTarget.DragEnforcer mDragEnforcer; - - private final static PorterDuffXfermode sAddBlendMode = - new PorterDuffXfermode(PorterDuff.Mode.ADD); - - public CellLayout(Context context) { - this(context, null); - } - - public CellLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public CellLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mDragEnforcer = new DropTarget.DragEnforcer(context); - - // 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); - mLauncher = (Launcher) context; - - 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); - mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0); - mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0); - mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0); - mCountX = LauncherModel.getCellCountX(); - mCountY = LauncherModel.getCellCountY(); - mOccupied = new boolean[mCountX][mCountY]; - mTmpOccupied = new boolean[mCountX][mCountY]; - mPreviousReorderDirection[0] = INVALID_DIRECTION; - mPreviousReorderDirection[1] = INVALID_DIRECTION; - - a.recycle(); - - setAlwaysDrawnWithCacheEnabled(false); - - final Resources res = getResources(); - - mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo); - mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo); - - mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left); - mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right); - mForegroundPadding = - res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding); - - mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE * - res.getDimensionPixelSize(R.dimen.app_icon_size)); - - mNormalBackground.setFilterBitmap(true); - mActiveGlowBackground.setFilterBitmap(true); - - // Initialize the data structures used for the drag visualization. - - mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out - - - mDragCell[0] = mDragCell[1] = -1; - for (int i = 0; i < mDragOutlines.length; i++) { - mDragOutlines[i] = new Rect(-1, -1, -1, -1); - } - - // 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) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - 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(); - CellLayout.this.invalidate(mDragOutlines[thisIndex]); - } - } - }); - // 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(); - mForegroundRect = new Rect(); - - mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context); - mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap); - addView(mShortcutsAndWidgets); - } - - static int widthInPortrait(Resources r, int numCells) { - // We use this method from Workspace to figure out how many rows/columns Launcher should - // have. We ignore the left/right padding on CellLayout because it turns out in our design - // the padding extends outside the visible screen size, but it looked fine anyway. - int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); - int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap), - r.getDimensionPixelSize(R.dimen.workspace_height_gap)); - - return minGap * (numCells - 1) + cellWidth * numCells; - } - - static int heightInLandscape(Resources r, int numCells) { - // We use this method from Workspace to figure out how many rows/columns Launcher should - // have. We ignore the left/right padding on CellLayout because it turns out in our design - // the padding extends outside the visible screen size, but it looked fine anyway. - int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); - int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap), - r.getDimensionPixelSize(R.dimen.workspace_height_gap)); - - return minGap * (numCells - 1) + cellHeight * numCells; - } - - public void enableHardwareLayers() { - mShortcutsAndWidgets.enableHardwareLayers(); - } - - public void setGridSize(int x, int y) { - mCountX = x; - mCountY = y; - mOccupied = new boolean[mCountX][mCountY]; - mTmpOccupied = new boolean[mCountX][mCountY]; - mTempRectStack.clear(); - requestLayout(); - } - - private void invalidateBubbleTextView(BubbleTextView icon) { - final int padding = icon.getPressedOrFocusedBackgroundPadding(); - invalidate(icon.getLeft() + getPaddingLeft() - padding, - icon.getTop() + getPaddingTop() - padding, - icon.getRight() + getPaddingLeft() + padding, - icon.getBottom() + getPaddingTop() + padding); - } - - void setOverScrollAmount(float r, boolean left) { - if (left && mOverScrollForegroundDrawable != mOverScrollLeft) { - mOverScrollForegroundDrawable = mOverScrollLeft; - } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) { - mOverScrollForegroundDrawable = mOverScrollRight; - } - - mForegroundAlpha = (int) Math.round((r * 255)); - mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha); - invalidate(); - } - - void setPressedOrFocusedIcon(BubbleTextView icon) { - // We draw the pressed or focused BubbleTextView's background in CellLayout because it - // requires an expanded clip rect (due to the glow's blur radius) - BubbleTextView oldIcon = mPressedOrFocusedIcon; - mPressedOrFocusedIcon = icon; - if (oldIcon != null) { - invalidateBubbleTextView(oldIcon); - } - if (mPressedOrFocusedIcon != null) { - invalidateBubbleTextView(mPressedOrFocusedIcon); - } - } - - void setIsDragOverlapping(boolean isDragOverlapping) { - if (mIsDragOverlapping != isDragOverlapping) { - mIsDragOverlapping = isDragOverlapping; - invalidate(); - } - } - - boolean getIsDragOverlapping() { - return mIsDragOverlapping; - } - - protected void setOverscrollTransformsDirty(boolean dirty) { - mScrollingTransformsDirty = dirty; - } - - protected void resetOverscrollTransforms() { - if (mScrollingTransformsDirty) { - setOverscrollTransformsDirty(false); - setTranslationX(0); - setRotationY(0); - // It doesn't matter if we pass true or false here, the important thing is that we - // pass 0, which results in the overscroll drawable not being drawn any more. - setOverScrollAmount(0, false); - setPivotX(getMeasuredWidth() / 2); - setPivotY(getMeasuredHeight() / 2); - } - } - - @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 (mIsDragOverlapping) { - // In the mini case, we draw the active_glow bg *over* the active background - bg = mActiveGlowBackground; - } else { - bg = mNormalBackground; - } - - bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255)); - bg.setBounds(mBackgroundRect); - bg.draw(canvas); - } - - final Paint paint = mDragOutlinePaint; - for (int i = 0; i < mDragOutlines.length; i++) { - final float alpha = mDragOutlineAlphas[i]; - if (alpha > 0) { - final Rect r = mDragOutlines[i]; - final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); - paint.setAlpha((int)(alpha + .5f)); - canvas.drawBitmap(b, null, r, paint); - } - } - - // We draw the pressed or focused BubbleTextView's background in CellLayout because it - // requires an expanded clip rect (due to the glow's blur radius) - if (mPressedOrFocusedIcon != null) { - final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding(); - final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground(); - if (b != null) { - canvas.drawBitmap(b, - mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding, - mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding, - null); - } - } - - if (DEBUG_VISUALIZE_OCCUPIED) { - int[] pt = new int[2]; - ColorDrawable cd = new ColorDrawable(Color.RED); - cd.setBounds(0, 0, mCellWidth, mCellHeight); - for (int i = 0; i < mCountX; i++) { - for (int j = 0; j < mCountY; j++) { - if (mOccupied[i][j]) { - cellToPoint(i, j, pt); - canvas.save(); - canvas.translate(pt[0], pt[1]); - cd.draw(canvas); - canvas.restore(); - } - } - } - } - - int previewOffset = FolderRingAnimator.sPreviewSize; - - // The folder outer / inner ring image(s) - for (int i = 0; i < mFolderOuterRings.size(); i++) { - FolderRingAnimator fra = mFolderOuterRings.get(i); - - // Draw outer ring - Drawable d = FolderRingAnimator.sSharedOuterRingDrawable; - int width = (int) fra.getOuterRingSize(); - int height = width; - cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); - - int centerX = mTempLocation[0] + mCellWidth / 2; - int centerY = mTempLocation[1] + previewOffset / 2; - - canvas.save(); - canvas.translate(centerX - width / 2, centerY - height / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); - - // Draw inner ring - d = FolderRingAnimator.sSharedInnerRingDrawable; - width = (int) fra.getInnerRingSize(); - height = width; - cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); - - centerX = mTempLocation[0] + mCellWidth / 2; - centerY = mTempLocation[1] + previewOffset / 2; - canvas.save(); - canvas.translate(centerX - width / 2, centerY - width / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); - } - - if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) { - Drawable d = FolderIcon.sSharedFolderLeaveBehind; - int width = d.getIntrinsicWidth(); - int height = d.getIntrinsicHeight(); - - cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation); - int centerX = mTempLocation[0] + mCellWidth / 2; - int centerY = mTempLocation[1] + previewOffset / 2; - - canvas.save(); - canvas.translate(centerX - width / 2, centerY - width / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (mForegroundAlpha > 0) { - mOverScrollForegroundDrawable.setBounds(mForegroundRect); - Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint(); - p.setXfermode(sAddBlendMode); - mOverScrollForegroundDrawable.draw(canvas); - p.setXfermode(null); - } - } - - public void showFolderAccept(FolderRingAnimator fra) { - mFolderOuterRings.add(fra); - } - - public void hideFolderAccept(FolderRingAnimator fra) { - if (mFolderOuterRings.contains(fra)) { - mFolderOuterRings.remove(fra); - } - invalidate(); - } - - public void setFolderLeaveBehindCell(int x, int y) { - mFolderLeaveBehindCell[0] = x; - mFolderLeaveBehindCell[1] = y; - invalidate(); - } - - public void clearFolderLeaveBehind() { - mFolderLeaveBehindCell[0] = -1; - mFolderLeaveBehindCell[1] = -1; - invalidate(); - } - - @Override - public boolean shouldDelayChildPressedState() { - return false; - } - - @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 void setOnInterceptTouchListener(View.OnTouchListener listener) { - mInterceptTouchListener = listener; - } - - int getCountX() { - return mCountX; - } - - int getCountY() { - return mCountY; - } - - public void setIsHotseat(boolean isHotseat) { - mIsHotseat = isHotseat; - } - - public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, - boolean markCells) { - final LayoutParams lp = params; - - // Hotseat icons - remove text - if (child instanceof BubbleTextView) { - BubbleTextView bubbleChild = (BubbleTextView) child; - - Resources res = getResources(); - if (mIsHotseat) { - bubbleChild.setTextColor(res.getColor(android.R.color.transparent)); - } else { - bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color)); - } - } - - // Generate an id for each view, this assumes we have at most 256x256 cells - // per workspace screen - 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); - - mShortcutsAndWidgets.addView(child, index, lp); - - if (markCells) markCellsAsOccupiedForView(child); - - return true; - } - return false; - } - - @Override - public void removeAllViews() { - clearOccupiedCells(); - mShortcutsAndWidgets.removeAllViews(); - } - - @Override - public void removeAllViewsInLayout() { - if (mShortcutsAndWidgets.getChildCount() > 0) { - clearOccupiedCells(); - mShortcutsAndWidgets.removeAllViewsInLayout(); - } - } - - public void removeViewWithoutMarkingCells(View view) { - mShortcutsAndWidgets.removeView(view); - } - - @Override - public void removeView(View view) { - markCellsAsUnoccupiedForView(view); - mShortcutsAndWidgets.removeView(view); - } - - @Override - public void removeViewAt(int index) { - markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index)); - mShortcutsAndWidgets.removeViewAt(index); - } - - @Override - public void removeViewInLayout(View view) { - markCellsAsUnoccupiedForView(view); - mShortcutsAndWidgets.removeViewInLayout(view); - } - - @Override - public void removeViews(int start, int count) { - for (int i = start; i < start + count; i++) { - markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i)); - } - mShortcutsAndWidgets.removeViews(start, count); - } - - @Override - public void removeViewsInLayout(int start, int count) { - for (int i = start; i < start + count; i++) { - markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i)); - } - mShortcutsAndWidgets.removeViewsInLayout(start, count); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); - } - - public void setTagToCellInfoForPoint(int touchX, int touchY) { - final CellInfo cellInfo = mCellInfo; - Rect frame = mRect; - final int x = touchX + getScrollX(); - final int y = touchY + getScrollY(); - final int count = mShortcutsAndWidgets.getChildCount(); - - boolean found = false; - for (int i = count - 1; i >= 0; i--) { - final View child = mShortcutsAndWidgets.getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) && - lp.isLockedToGrid) { - child.getHitRect(frame); - - float scale = child.getScaleX(); - frame = new Rect(child.getLeft(), child.getTop(), child.getRight(), - child.getBottom()); - // The child hit rect is relative to the CellLayoutChildren parent, so we need to - // offset that by this CellLayout's padding to test an (x,y) point that is relative - // to this view. - frame.offset(getPaddingLeft(), getPaddingTop()); - frame.inset((int) (frame.width() * (1f - scale) / 2), - (int) (frame.height() * (1f - scale) / 2)); - - if (frame.contains(x, y)) { - cellInfo.cell = child; - cellInfo.cellX = lp.cellX; - cellInfo.cellY = lp.cellY; - cellInfo.spanX = lp.cellHSpan; - cellInfo.spanY = lp.cellVSpan; - found = true; - break; - } - } - } - - mLastDownOnOccupiedCell = found; - - if (!found) { - final int cellXY[] = mTmpXY; - pointToCellExact(x, y, cellXY); - - cellInfo.cell = null; - cellInfo.cellX = cellXY[0]; - cellInfo.cellY = cellXY[1]; - cellInfo.spanX = 1; - cellInfo.spanY = 1; - } - setTag(cellInfo); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - // First we clear the tag to ensure that on every touch down we start with a fresh slate, - // even in the case where we return early. Not clearing here was causing bugs whereby on - // long-press we'd end up picking up an item from a previous drag operation. - final int action = ev.getAction(); - - if (action == MotionEvent.ACTION_DOWN) { - clearTagCellInfo(); - } - - if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { - return true; - } - - if (action == MotionEvent.ACTION_DOWN) { - setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); - } - - return false; - } - - private void clearTagCellInfo() { - final CellInfo cellInfo = mCellInfo; - cellInfo.cell = null; - cellInfo.cellX = -1; - cellInfo.cellY = -1; - cellInfo.spanX = 0; - cellInfo.spanY = 0; - setTag(cellInfo); - } - - public CellInfo getTag() { - return (CellInfo) super.getTag(); - } - - /** - * 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 int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - - result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); - result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); - - 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 - * @param y Y coordinate of the point - * @param result Array of 2 ints to hold the x and y coordinate of the cell - */ - void pointToCellRounded(int x, int y, int[] result) { - pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result); - } - - /** - * Given a cell coordinate, return the point that represents the upper left corner of that 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 int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - - result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); - result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); - } - - /** - * Given a cell coordinate, return the point that represents the center 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 cellToCenterPoint(int cellX, int cellY, int[] result) { - regionToCenterPoint(cellX, cellY, 1, 1, result); - } - - /** - * Given a cell coordinate and span return the point that represents the center of the regio - * - * @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 regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) { - final int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + - (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2; - result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + - (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2; - } - - /** - * Given a cell coordinate and span fills out a corresponding pixel rect - * - * @param cellX X coordinate of the cell - * @param cellY Y coordinate of the cell - * @param result Rect in which to write the result - */ - void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) { - final int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - final int left = hStartPadding + cellX * (mCellWidth + mWidthGap); - final int top = vStartPadding + cellY * (mCellHeight + mHeightGap); - result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap), - top + (spanY * mCellHeight + (spanY - 1) * mHeightGap)); - } - - public float getDistanceFromCell(float x, float y, int[] cell) { - cellToCenterPoint(cell[0], cell[1], mTmpPoint); - float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) + - Math.pow(y - mTmpPoint[1], 2)); - return distance; - } - - int getCellWidth() { - return mCellWidth; - } - - int getCellHeight() { - return mCellHeight; - } - - int getWidthGap() { - return mWidthGap; - } - - int getHeightGap() { - return mHeightGap; - } - - Rect getContentRect(Rect r) { - if (r == null) { - r = new Rect(); - } - int left = getPaddingLeft(); - int top = getPaddingTop(); - int right = left + getWidth() - getPaddingLeft() - getPaddingRight(); - int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom(); - r.set(left, top, right, bottom); - return r; - } - - static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight, - int countX, int countY, int orientation) { - int numWidthGaps = countX - 1; - int numHeightGaps = countY - 1; - - int widthGap; - int heightGap; - int cellWidth; - int cellHeight; - int paddingLeft; - int paddingRight; - int paddingTop; - int paddingBottom; - - int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap); - if (orientation == LANDSCAPE) { - cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land); - cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land); - widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land); - heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land); - paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land); - paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land); - paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land); - paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land); - } else { - // PORTRAIT - cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port); - cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port); - widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port); - heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port); - paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port); - paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port); - paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port); - paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port); - } - - if (widthGap < 0 || heightGap < 0) { - int hSpace = measureWidth - paddingLeft - paddingRight; - int vSpace = measureHeight - paddingTop - paddingBottom; - int hFreeSpace = hSpace - (countX * cellWidth); - int vFreeSpace = vSpace - (countY * cellHeight); - widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); - heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); - } - metrics.set(cellWidth, cellHeight, widthGap, heightGap); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - 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"); - } - - int numWidthGaps = mCountX - 1; - int numHeightGaps = mCountY - 1; - - if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { - int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight(); - int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); - int hFreeSpace = hSpace - (mCountX * mCellWidth); - int vFreeSpace = vSpace - (mCountY * mCellHeight); - mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); - mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); - mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap); - } else { - mWidthGap = mOriginalWidthGap; - mHeightGap = mOriginalHeightGap; - } - - // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY - int newWidth = widthSpecSize; - int newHeight = heightSpecSize; - if (widthSpecMode == MeasureSpec.AT_MOST) { - newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) + - ((mCountX - 1) * mWidthGap); - newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) + - ((mCountY - 1) * mHeightGap); - setMeasuredDimension(newWidth, newHeight); - } - - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() - - getPaddingRight(), MeasureSpec.EXACTLY); - int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() - - getPaddingBottom(), 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(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - child.layout(getPaddingLeft(), getPaddingTop(), - r - l - getPaddingRight(), b - t - getPaddingBottom()); - } - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mBackgroundRect.set(0, 0, w, h); - mForegroundRect.set(mForegroundPadding, mForegroundPadding, - w - 2 * mForegroundPadding, h - 2 * mForegroundPadding); - } - - @Override - protected void setChildrenDrawingCacheEnabled(boolean enabled) { - mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled); - } - - @Override - protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { - mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled); - } - - public float getBackgroundAlpha() { - return mBackgroundAlpha; - } - - public void setBackgroundAlphaMultiplier(float multiplier) { - if (mBackgroundAlphaMultiplier != multiplier) { - mBackgroundAlphaMultiplier = multiplier; - invalidate(); - } - } - - public float getBackgroundAlphaMultiplier() { - return mBackgroundAlphaMultiplier; - } - - public void setBackgroundAlpha(float alpha) { - if (mBackgroundAlpha != alpha) { - mBackgroundAlpha = alpha; - invalidate(); - } - } - - public void setShortcutAndWidgetAlpha(float alpha) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - getChildAt(i).setAlpha(alpha); - } - } - - public ShortcutAndWidgetContainer getShortcutsAndWidgets() { - if (getChildCount() > 0) { - return (ShortcutAndWidgetContainer) getChildAt(0); - } - return null; - } - - public View getChildAt(int x, int y) { - return mShortcutsAndWidgets.getChildAt(x, y); - } - - public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, - int delay, boolean permanent, boolean adjustOccupied) { - ShortcutAndWidgetContainer clc = getShortcutsAndWidgets(); - boolean[][] occupied = mOccupied; - if (!permanent) { - occupied = mTmpOccupied; - } - - if (clc.indexOfChild(child) != -1) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - final ItemInfo info = (ItemInfo) child.getTag(); - - // We cancel any existing animations - if (mReorderAnimators.containsKey(lp)) { - mReorderAnimators.get(lp).cancel(); - mReorderAnimators.remove(lp); - } - - final int oldX = lp.x; - final int oldY = lp.y; - if (adjustOccupied) { - occupied[lp.cellX][lp.cellY] = false; - occupied[cellX][cellY] = true; - } - lp.isLockedToGrid = true; - if (permanent) { - lp.cellX = info.cellX = cellX; - lp.cellY = info.cellY = cellY; - } else { - lp.tmpCellX = cellX; - lp.tmpCellY = cellY; - } - clc.setupLp(lp); - lp.isLockedToGrid = false; - final int newX = lp.x; - final int newY = lp.y; - - lp.x = oldX; - lp.y = oldY; - - // Exit early if we're not actually moving the view - if (oldX == newX && oldY == newY) { - lp.isLockedToGrid = true; - return true; - } - - ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - va.setDuration(duration); - mReorderAnimators.put(lp, va); - - va.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float r = ((Float) animation.getAnimatedValue()).floatValue(); - lp.x = (int) ((1 - r) * oldX + r * newX); - lp.y = (int) ((1 - r) * oldY + r * newY); - child.requestLayout(); - } - }); - va.addListener(new AnimatorListenerAdapter() { - boolean cancelled = false; - public void onAnimationEnd(Animator animation) { - // If the animation was cancelled, it means that another animation - // has interrupted this one, and we don't want to lock the item into - // place just yet. - if (!cancelled) { - lp.isLockedToGrid = true; - child.requestLayout(); - } - if (mReorderAnimators.containsKey(lp)) { - mReorderAnimators.remove(lp); - } - } - public void onAnimationCancel(Animator animation) { - cancelled = true; - } - }); - va.setStartDelay(delay); - va.start(); - return true; - } - return false; - } - - /** - * 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 cellX, - int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) { - final int oldDragCellX = mDragCell[0]; - final int oldDragCellY = mDragCell[1]; - - if (v != null && dragOffset == null) { - mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2)); - } else { - mDragCenter.set(originX, originY); - } - - if (dragOutline == null && v == null) { - return; - } - - if (cellX != oldDragCellX || cellY != oldDragCellY) { - mDragCell[0] = cellX; - mDragCell[1] = cellY; - // Find the top left corner of the rect the object will occupy - final int[] topLeft = mTmpPoint; - cellToPoint(cellX, cellY, topLeft); - - int left = topLeft[0]; - int top = topLeft[1]; - - if (v != null && dragOffset == null) { - // When drawing the drag outline, it did not account for margin offsets - // added by the view's parent. - MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); - left += lp.leftMargin; - top += lp.topMargin; - - // Offsets due to the size difference between the View and the dragOutline. - // There is a size difference to account for the outer blur, which may lie - // outside the bounds of the view. - top += (v.getHeight() - dragOutline.getHeight()) / 2; - // We center about the x axis - left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragOutline.getWidth()) / 2; - } else { - if (dragOffset != null && dragRegion != null) { - // Center the drag region *horizontally* in the cell and apply a drag - // outline offset - left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragRegion.width()) / 2; - top += dragOffset.y; - } else { - // Center the drag outline in the cell - left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragOutline.getWidth()) / 2; - top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) - - dragOutline.getHeight()) / 2; - } - } - final int oldIndex = mDragOutlineCurrent; - mDragOutlineAnims[oldIndex].animateOut(); - mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; - Rect r = mDragOutlines[mDragOutlineCurrent]; - r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); - if (resize) { - cellToRect(cellX, cellY, spanX, spanY, r); - } - - mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); - mDragOutlineAnims[mDragOutlineCurrent].animateIn(); - } - } - - public void clearDragOutlines() { - final int oldIndex = mDragOutlineCurrent; - mDragOutlineAnims[oldIndex].animateOut(); - mDragCell[0] = mDragCell[1] = -1; - } - - /** - * 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 minSpanX The minimum horizontal span required - * @param minSpanY The minimum vertical span required - * @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 minSpanX, int minSpanY, int spanX, - int spanY, int[] result, int[] resultSpan) { - return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, - result, resultSpan); - } - - /** - * 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 ignoreOccupied If true, the result can be an occupied cell - * @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[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, - boolean ignoreOccupied, int[] result) { - return findNearestArea(pixelX, pixelY, spanX, spanY, - spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied); - } - - private final Stack mTempRectStack = new Stack(); - private void lazyInitTempRectStack() { - if (mTempRectStack.isEmpty()) { - for (int i = 0; i < mCountX * mCountY; i++) { - mTempRectStack.push(new Rect()); - } - } - } - - private void recycleTempRects(Stack used) { - while (!used.isEmpty()) { - mTempRectStack.push(used.pop()); - } - } - - /** - * 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 minSpanX The minimum horizontal span required - * @param minSpanY The minimum vertical span required - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param ignoreOccupied If true, the result can be an occupied cell - * @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[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, - View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan, - boolean[][] occupied) { - lazyInitTempRectStack(); - // mark space take by ignoreView as available (method checks if ignoreView is null) - markCellsAsUnoccupiedForView(ignoreView, occupied); - - // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds - // to the center of the item, but we are searching based on the top-left cell, so - // we translate the point over to correspond to the top-left. - pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f; - pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f; - - // Keep track of best-scoring drop area - final int[] bestXY = result != null ? result : new int[2]; - double bestDistance = Double.MAX_VALUE; - final Rect bestRect = new Rect(-1, -1, -1, -1); - final Stack validRegions = new Stack(); - - final int countX = mCountX; - final int countY = mCountY; - - if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 || - spanX < minSpanX || spanY < minSpanY) { - return bestXY; - } - - for (int y = 0; y < countY - (minSpanY - 1); y++) { - inner: - for (int x = 0; x < countX - (minSpanX - 1); x++) { - int ySize = -1; - int xSize = -1; - if (ignoreOccupied) { - // First, let's see if this thing fits anywhere - for (int i = 0; i < minSpanX; i++) { - for (int j = 0; j < minSpanY; j++) { - if (occupied[x + i][y + j]) { - continue inner; - } - } - } - xSize = minSpanX; - ySize = minSpanY; - - // We know that the item will fit at _some_ acceptable size, now let's see - // how big we can make it. We'll alternate between incrementing x and y spans - // until we hit a limit. - boolean incX = true; - boolean hitMaxX = xSize >= spanX; - boolean hitMaxY = ySize >= spanY; - while (!(hitMaxX && hitMaxY)) { - if (incX && !hitMaxX) { - for (int j = 0; j < ySize; j++) { - if (x + xSize > countX -1 || occupied[x + xSize][y + j]) { - // We can't move out horizontally - hitMaxX = true; - } - } - if (!hitMaxX) { - xSize++; - } - } else if (!hitMaxY) { - for (int i = 0; i < xSize; i++) { - if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) { - // We can't move out vertically - hitMaxY = true; - } - } - if (!hitMaxY) { - ySize++; - } - } - hitMaxX |= xSize >= spanX; - hitMaxY |= ySize >= spanY; - incX = !incX; - } - incX = true; - hitMaxX = xSize >= spanX; - hitMaxY = ySize >= spanY; - } - final int[] cellXY = mTmpXY; - cellToCenterPoint(x, y, cellXY); - - // We verify that the current rect is not a sub-rect of any of our previous - // candidates. In this case, the current rect is disqualified in favour of the - // containing rect. - Rect currentRect = mTempRectStack.pop(); - currentRect.set(x, y, x + xSize, y + ySize); - boolean contained = false; - for (Rect r : validRegions) { - if (r.contains(currentRect)) { - contained = true; - break; - } - } - validRegions.push(currentRect); - double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) - + Math.pow(cellXY[1] - pixelY, 2)); - - if ((distance <= bestDistance && !contained) || - currentRect.contains(bestRect)) { - bestDistance = distance; - bestXY[0] = x; - bestXY[1] = y; - if (resultSpan != null) { - resultSpan[0] = xSize; - resultSpan[1] = ySize; - } - bestRect.set(currentRect); - } - } - } - // re-mark space taken by ignoreView as occupied - markCellsAsOccupiedForView(ignoreView, occupied); - - // Return -1, -1 if no suitable location found - if (bestDistance == Double.MAX_VALUE) { - bestXY[0] = -1; - bestXY[1] = -1; - } - recycleTempRects(validRegions); - return bestXY; - } - - /** - * Find a vacant area that will fit the given bounds nearest the requested - * cell location, and will also weigh in a suggested direction vector of the - * desired location. This method computers distance based on unit grid distances, - * not pixel distances. - * - * @param cellX The X cell nearest to which you want to search for a vacant area. - * @param cellY The Y cell nearest which you want to search for a vacant area. - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param direction The favored direction in which the views should move from x, y - * @param exactDirectionOnly If this parameter is true, then only solutions where the direction - * matches exactly. Otherwise we find the best matching direction. - * @param occoupied The array which represents which cells in the CellLayout are occupied - * @param blockOccupied The array which represents which cells in the specified block (cellX, - * cellY, spanX, spanY) are occupied. This is used when try to move a group of views. - * @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. - */ - private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, - boolean[][] occupied, boolean blockOccupied[][], int[] result) { - // Keep track of best-scoring drop area - final int[] bestXY = result != null ? result : new int[2]; - float bestDistance = Float.MAX_VALUE; - int bestDirectionScore = Integer.MIN_VALUE; - - final int countX = mCountX; - final int countY = mCountY; - - for (int y = 0; y < countY - (spanY - 1); y++) { - inner: - for (int x = 0; x < countX - (spanX - 1); x++) { - // First, let's see if this thing fits anywhere - for (int i = 0; i < spanX; i++) { - for (int j = 0; j < spanY; j++) { - if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) { - continue inner; - } - } - } - - float distance = (float) - Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); - int[] curDirection = mTmpPoint; - computeDirectionVector(x - cellX, y - cellY, curDirection); - // The direction score is just the dot product of the two candidate direction - // and that passed in. - int curDirectionScore = direction[0] * curDirection[0] + - direction[1] * curDirection[1]; - boolean exactDirectionOnly = false; - boolean directionMatches = direction[0] == curDirection[0] && - direction[0] == curDirection[0]; - if ((directionMatches || !exactDirectionOnly) && - Float.compare(distance, bestDistance) < 0 || (Float.compare(distance, - bestDistance) == 0 && curDirectionScore > bestDirectionScore)) { - bestDistance = distance; - bestDirectionScore = curDirectionScore; - bestXY[0] = x; - bestXY[1] = y; - } - } - } - - // Return -1, -1 if no suitable location found - if (bestDistance == Float.MAX_VALUE) { - bestXY[0] = -1; - bestXY[1] = -1; - } - return bestXY; - } - - private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY, - int[] direction,boolean[][] occupied, - boolean blockOccupied[][], int[] result) { - // Keep track of best-scoring drop area - final int[] bestXY = result != null ? result : new int[2]; - bestXY[0] = -1; - bestXY[1] = -1; - float bestDistance = Float.MAX_VALUE; - - // We use this to march in a single direction - if ((direction[0] != 0 && direction[1] != 0) || - (direction[0] == 0 && direction[1] == 0)) { - return bestXY; - } - - // This will only incrememnet one of x or y based on the assertion above - int x = cellX + direction[0]; - int y = cellY + direction[1]; - while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) { - - boolean fail = false; - for (int i = 0; i < spanX; i++) { - for (int j = 0; j < spanY; j++) { - if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) { - fail = true; - } - } - } - if (!fail) { - float distance = (float) - Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); - if (Float.compare(distance, bestDistance) < 0) { - bestDistance = distance; - bestXY[0] = x; - bestXY[1] = y; - } - } - x += direction[0]; - y += direction[1]; - } - return bestXY; - } - - private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, - int[] direction, ItemConfiguration currentState) { - CellAndSpan c = currentState.map.get(v); - boolean success = false; - markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); - markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); - - findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation); - - if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { - c.x = mTempLocation[0]; - c.y = mTempLocation[1]; - success = true; - - } - markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); - return success; - } - - // This method looks in the specified direction to see if there is an additional view - // immediately adjecent in that direction - private boolean addViewInDirection(ArrayList views, Rect boundingRect, int[] direction, - boolean[][] occupied, View dragView, ItemConfiguration currentState) { - boolean found = false; - - int childCount = mShortcutsAndWidgets.getChildCount(); - Rect r0 = new Rect(boundingRect); - Rect r1 = new Rect(); - - int deltaX = 0; - int deltaY = 0; - if (direction[1] < 0) { - r0.set(r0.left, r0.top - 1, r0.right, r0.bottom); - deltaY = -1; - } else if (direction[1] > 0) { - r0.set(r0.left, r0.top, r0.right, r0.bottom + 1); - deltaY = 1; - } else if (direction[0] < 0) { - r0.set(r0.left - 1, r0.top, r0.right, r0.bottom); - deltaX = -1; - } else if (direction[0] > 0) { - r0.set(r0.left, r0.top, r0.right + 1, r0.bottom); - deltaX = 1; - } - - for (int i = 0; i < childCount; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - if (views.contains(child) || child == dragView) continue; - CellAndSpan c = currentState.map.get(child); - - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); - if (Rect.intersects(r0, r1)) { - if (!lp.canReorder) { - return false; - } - boolean pushed = false; - for (int x = c.x; x < c.x + c.spanX; x++) { - for (int y = c.y; y < c.y + c.spanY; y++) { - boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX - && y - deltaY >= 0 && y - deltaY < mCountY; - if (inBounds && occupied[x - deltaX][y - deltaY]) { - pushed = true; - } - } - } - if (pushed) { - views.add(child); - boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY); - found = true; - } - } - } - return found; - } - - private boolean addViewsToTempLocation(ArrayList views, Rect rectOccupiedByPotentialDrop, - int[] direction, boolean push, View dragView, ItemConfiguration currentState) { - if (views.size() == 0) return true; - - boolean success = false; - Rect boundingRect = null; - // We construct a rect which represents the entire group of views passed in - for (View v: views) { - CellAndSpan c = currentState.map.get(v); - if (boundingRect == null) { - boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY); - } else { - boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY); - } - } - - @SuppressWarnings("unchecked") - ArrayList dup = (ArrayList) views.clone(); - // We try and expand the group of views in the direction vector passed, based on - // whether they are physically adjacent, ie. based on "push mechanics". - while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView, - currentState)) { - } - - // Mark the occupied state as false for the group of views we want to move. - for (View v: dup) { - CellAndSpan c = currentState.map.get(v); - markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); - } - - boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()]; - int top = boundingRect.top; - int left = boundingRect.left; - // We mark more precisely which parts of the bounding rect are truly occupied, allowing - // for tetris-style interlocking. - for (View v: dup) { - CellAndSpan c = currentState.map.get(v); - markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true); - } - - markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); - - if (push) { - findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(), - boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); - } else { - findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), - boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); - } - - // If we successfuly found a location by pushing the block of views, we commit it - if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { - int deltaX = mTempLocation[0] - boundingRect.left; - int deltaY = mTempLocation[1] - boundingRect.top; - for (View v: dup) { - CellAndSpan c = currentState.map.get(v); - c.x += deltaX; - c.y += deltaY; - } - success = true; - } - - // In either case, we set the occupied array as marked for the location of the views - for (View v: dup) { - CellAndSpan c = currentState.map.get(v); - markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); - } - return success; - } - - private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) { - markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value); - } - - // This method tries to find a reordering solution which satisfies the push mechanic by trying - // to push items in each of the cardinal directions, in an order based on the direction vector - // passed. - private boolean attemptPushInDirection(ArrayList intersectingViews, Rect occupied, - int[] direction, View ignoreView, ItemConfiguration solution) { - if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) { - // If the direction vector has two non-zero components, we try pushing - // separately in each of the components. - int temp = direction[1]; - direction[1] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - direction[1] = temp; - temp = direction[0]; - direction[0] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - // Revert the direction - direction[0] = temp; - - // Now we try pushing in each component of the opposite direction - direction[0] *= -1; - direction[1] *= -1; - temp = direction[1]; - direction[1] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - - direction[1] = temp; - temp = direction[0]; - direction[0] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - // revert the direction - direction[0] = temp; - direction[0] *= -1; - direction[1] *= -1; - - } else { - // If the direction vector has a single non-zero component, we push first in the - // direction of the vector - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - - // Then we try the opposite direction - direction[0] *= -1; - direction[1] *= -1; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - // Switch the direction back - direction[0] *= -1; - direction[1] *= -1; - - // If we have failed to find a push solution with the above, then we try - // to find a solution by pushing along the perpendicular axis. - - // Swap the components - int temp = direction[1]; - direction[1] = direction[0]; - direction[0] = temp; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - - // Then we try the opposite direction - direction[0] *= -1; - direction[1] *= -1; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, - ignoreView, solution)) { - return true; - } - // Switch the direction back - direction[0] *= -1; - direction[1] *= -1; - - // Swap the components back - temp = direction[1]; - direction[1] = direction[0]; - direction[0] = temp; - } - return false; - } - - private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, - View ignoreView, ItemConfiguration solution) { - // Return early if get invalid cell positions - if (cellX < 0 || cellY < 0) return false; - - mIntersectingViews.clear(); - mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY); - - // Mark the desired location of the view currently being dragged. - if (ignoreView != null) { - CellAndSpan c = solution.map.get(ignoreView); - if (c != null) { - c.x = cellX; - c.y = cellY; - } - } - Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); - Rect r1 = new Rect(); - for (View child: solution.map.keySet()) { - if (child == ignoreView) continue; - CellAndSpan c = solution.map.get(child); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); - if (Rect.intersects(r0, r1)) { - if (!lp.canReorder) { - return false; - } - mIntersectingViews.add(child); - } - } - - // First we try to find a solution which respects the push mechanic. That is, - // we try to find a solution such that no displaced item travels through another item - // without also displacing that item. - if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView, - solution)) { - return true; - } - - // Next we try moving the views as a block, but without requiring the push mechanic. - if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView, - solution)) { - return true; - } - - // Ok, they couldn't move as a block, let's move them individually - for (View v : mIntersectingViews) { - if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) { - return false; - } - } - return true; - } - - /* - * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between - * the provided point and the provided cell - */ - private void computeDirectionVector(float deltaX, float deltaY, int[] result) { - double angle = Math.atan(((float) deltaY) / deltaX); - - result[0] = 0; - result[1] = 0; - if (Math.abs(Math.cos(angle)) > 0.5f) { - result[0] = (int) Math.signum(deltaX); - } - if (Math.abs(Math.sin(angle)) > 0.5f) { - result[1] = (int) Math.signum(deltaY); - } - } - - private void copyOccupiedArray(boolean[][] occupied) { - for (int i = 0; i < mCountX; i++) { - for (int j = 0; j < mCountY; j++) { - occupied[i][j] = mOccupied[i][j]; - } - } - } - - ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, - int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) { - // Copy the current state into the solution. This solution will be manipulated as necessary. - copyCurrentStateToSolution(solution, false); - // Copy the current occupied array into the temporary occupied array. This array will be - // manipulated as necessary to find a solution. - copyOccupiedArray(mTmpOccupied); - - // We find the nearest cell into which we would place the dragged item, assuming there's - // nothing in its way. - int result[] = new int[2]; - result = findNearestArea(pixelX, pixelY, spanX, spanY, result); - - boolean success = false; - // First we try the exact nearest position of the item being dragged, - // we will then want to try to move this around to other neighbouring positions - success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView, - solution); - - if (!success) { - // We try shrinking the widget down to size in an alternating pattern, shrink 1 in - // x, then 1 in y etc. - if (spanX > minSpanX && (minSpanY == spanY || decX)) { - return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction, - dragView, false, solution); - } else if (spanY > minSpanY) { - return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction, - dragView, true, solution); - } - solution.isSolution = false; - } else { - solution.isSolution = true; - solution.dragViewX = result[0]; - solution.dragViewY = result[1]; - solution.dragViewSpanX = spanX; - solution.dragViewSpanY = spanY; - } - return solution; - } - - private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { - int childCount = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - CellAndSpan c; - if (temp) { - c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan); - } else { - c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan); - } - solution.map.put(child, c); - } - } - - private void copySolutionToTempState(ItemConfiguration solution, View dragView) { - for (int i = 0; i < mCountX; i++) { - for (int j = 0; j < mCountY; j++) { - mTmpOccupied[i][j] = false; - } - } - - int childCount = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - if (child == dragView) continue; - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - CellAndSpan c = solution.map.get(child); - if (c != null) { - lp.tmpCellX = c.x; - lp.tmpCellY = c.y; - lp.cellHSpan = c.spanX; - lp.cellVSpan = c.spanY; - markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); - } - } - markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, - solution.dragViewSpanY, mTmpOccupied, true); - } - - private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean - commitDragView) { - - boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied; - for (int i = 0; i < mCountX; i++) { - for (int j = 0; j < mCountY; j++) { - occupied[i][j] = false; - } - } - - int childCount = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - if (child == dragView) continue; - CellAndSpan c = solution.map.get(child); - if (c != null) { - animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0, - DESTRUCTIVE_REORDER, false); - markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true); - } - } - if (commitDragView) { - markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, - solution.dragViewSpanY, occupied, true); - } - } - - // This method starts or changes the reorder hint animations - private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) { - int childCount = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - if (child == dragView) continue; - CellAndSpan c = solution.map.get(child); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (c != null) { - ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY, - c.x, c.y, c.spanX, c.spanY); - rha.animate(); - } - } - } - - // Class which represents the reorder hint animations. These animations show that an item is - // in a temporary state, and hint at where the item will return to. - class ReorderHintAnimation { - View child; - float finalDeltaX; - float finalDeltaY; - float initDeltaX; - float initDeltaY; - float finalScale; - float initScale; - private static final int DURATION = 300; - Animator a; - - public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1, - int spanX, int spanY) { - regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint); - final int x0 = mTmpPoint[0]; - final int y0 = mTmpPoint[1]; - regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint); - final int x1 = mTmpPoint[0]; - final int y1 = mTmpPoint[1]; - final int dX = x1 - x0; - final int dY = y1 - y0; - finalDeltaX = 0; - finalDeltaY = 0; - if (dX == dY && dX == 0) { - } else { - if (dY == 0) { - finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude; - } else if (dX == 0) { - finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude; - } else { - double angle = Math.atan( (float) (dY) / dX); - finalDeltaX = (int) (- Math.signum(dX) * - Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude)); - finalDeltaY = (int) (- Math.signum(dY) * - Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude)); - } - } - initDeltaX = child.getTranslationX(); - initDeltaY = child.getTranslationY(); - finalScale = 1.0f - 4.0f / child.getWidth(); - initScale = child.getScaleX(); - - child.setPivotY(child.getMeasuredHeight() * 0.5f); - child.setPivotX(child.getMeasuredWidth() * 0.5f); - this.child = child; - } - - void animate() { - if (mShakeAnimators.containsKey(child)) { - ReorderHintAnimation oldAnimation = mShakeAnimators.get(child); - oldAnimation.cancel(); - mShakeAnimators.remove(child); - if (finalDeltaX == 0 && finalDeltaY == 0) { - completeAnimationImmediately(); - return; - } - } - if (finalDeltaX == 0 && finalDeltaY == 0) { - return; - } - ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - a = va; - va.setRepeatMode(ValueAnimator.REVERSE); - va.setRepeatCount(ValueAnimator.INFINITE); - va.setDuration(DURATION); - va.setStartDelay((int) (Math.random() * 60)); - va.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float r = ((Float) animation.getAnimatedValue()).floatValue(); - float x = r * finalDeltaX + (1 - r) * initDeltaX; - float y = r * finalDeltaY + (1 - r) * initDeltaY; - child.setTranslationX(x); - child.setTranslationY(y); - float s = r * finalScale + (1 - r) * initScale; - child.setScaleX(s); - child.setScaleY(s); - } - }); - va.addListener(new AnimatorListenerAdapter() { - public void onAnimationRepeat(Animator animation) { - // We make sure to end only after a full period - initDeltaX = 0; - initDeltaY = 0; - initScale = 1.0f; - } - }); - mShakeAnimators.put(child, this); - va.start(); - } - - private void cancel() { - if (a != null) { - a.cancel(); - } - } - - private void completeAnimationImmediately() { - if (a != null) { - a.cancel(); - } - - AnimatorSet s = new AnimatorSet(); - a = s; - s.playTogether( - ObjectAnimator.ofFloat(child, "scaleX", 1f), - ObjectAnimator.ofFloat(child, "scaleY", 1f), - ObjectAnimator.ofFloat(child, "translationX", 0f), - ObjectAnimator.ofFloat(child, "translationY", 0f) - ); - s.setDuration(REORDER_ANIMATION_DURATION); - s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); - s.start(); - } - } - - private void completeAndClearReorderHintAnimations() { - for (ReorderHintAnimation a: mShakeAnimators.values()) { - a.completeAnimationImmediately(); - } - mShakeAnimators.clear(); - } - - private void commitTempPlacement() { - for (int i = 0; i < mCountX; i++) { - for (int j = 0; j < mCountY; j++) { - mOccupied[i][j] = mTmpOccupied[i][j]; - } - } - int childCount = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - ItemInfo info = (ItemInfo) child.getTag(); - // We do a null check here because the item info can be null in the case of the - // AllApps button in the hotseat. - if (info != null) { - info.cellX = lp.cellX = lp.tmpCellX; - info.cellY = lp.cellY = lp.tmpCellY; - info.spanX = lp.cellHSpan; - info.spanY = lp.cellVSpan; - } - } - mLauncher.getWorkspace().updateItemLocationsInDatabase(this); - } - - public void setUseTempCoords(boolean useTempCoords) { - int childCount = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < childCount; i++) { - LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams(); - lp.useTmpCoords = useTempCoords; - } - } - - ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY, - int spanX, int spanY, View dragView, ItemConfiguration solution) { - int[] result = new int[2]; - int[] resultSpan = new int[2]; - findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result, - resultSpan); - if (result[0] >= 0 && result[1] >= 0) { - copyCurrentStateToSolution(solution, false); - solution.dragViewX = result[0]; - solution.dragViewY = result[1]; - solution.dragViewSpanX = resultSpan[0]; - solution.dragViewSpanY = resultSpan[1]; - solution.isSolution = true; - } else { - solution.isSolution = false; - } - return solution; - } - - public void prepareChildForDrag(View child) { - markCellsAsUnoccupiedForView(child); - } - - /* This seems like it should be obvious and straight-forward, but when the direction vector - needs to match with the notion of the dragView pushing other views, we have to employ - a slightly more subtle notion of the direction vector. The question is what two points is - the vector between? The center of the dragView and its desired destination? Not quite, as - this doesn't necessarily coincide with the interaction of the dragView and items occupying - those cells. Instead we use some heuristics to often lock the vector to up, down, left - or right, which helps make pushing feel right. - */ - private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, - int spanY, View dragView, int[] resultDirection) { - int[] targetDestination = new int[2]; - - findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination); - Rect dragRect = new Rect(); - regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect); - dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY()); - - Rect dropRegionRect = new Rect(); - getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY, - dragView, dropRegionRect, mIntersectingViews); - - int dropRegionSpanX = dropRegionRect.width(); - int dropRegionSpanY = dropRegionRect.height(); - - regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(), - dropRegionRect.height(), dropRegionRect); - - int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX; - int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY; - - if (dropRegionSpanX == mCountX || spanX == mCountX) { - deltaX = 0; - } - if (dropRegionSpanY == mCountY || spanY == mCountY) { - deltaY = 0; - } - - if (deltaX == 0 && deltaY == 0) { - // No idea what to do, give a random direction. - resultDirection[0] = 1; - resultDirection[1] = 0; - } else { - computeDirectionVector(deltaX, deltaY, resultDirection); - } - } - - // For a given cell and span, fetch the set of views intersecting the region. - private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY, - View dragView, Rect boundingRect, ArrayList intersectingViews) { - if (boundingRect != null) { - boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY); - } - intersectingViews.clear(); - Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); - Rect r1 = new Rect(); - final int count = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < count; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - if (child == dragView) continue; - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan); - if (Rect.intersects(r0, r1)) { - mIntersectingViews.add(child); - if (boundingRect != null) { - boundingRect.union(r1); - } - } - } - } - - boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, - View dragView, int[] result) { - result = findNearestArea(pixelX, pixelY, spanX, spanY, result); - getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null, - mIntersectingViews); - return !mIntersectingViews.isEmpty(); - } - - void revertTempState() { - if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return; - final int count = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < count; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) { - lp.tmpCellX = lp.cellX; - lp.tmpCellY = lp.cellY; - animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION, - 0, false, false); - } - } - completeAndClearReorderHintAnimations(); - setItemPlacementDirty(false); - } - - boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, - View dragView, int[] direction, boolean commit) { - int[] pixelXY = new int[2]; - regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY); - - // First we determine if things have moved enough to cause a different layout - ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY, - spanX, spanY, direction, dragView, true, new ItemConfiguration()); - - setUseTempCoords(true); - if (swapSolution != null && swapSolution.isSolution) { - // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother - // committing anything or animating anything as we just want to determine if a solution - // exists - copySolutionToTempState(swapSolution, dragView); - setItemPlacementDirty(true); - animateItemsToSolution(swapSolution, dragView, commit); - - if (commit) { - commitTempPlacement(); - completeAndClearReorderHintAnimations(); - setItemPlacementDirty(false); - } else { - beginOrAdjustHintAnimations(swapSolution, dragView, - REORDER_ANIMATION_DURATION); - } - mShortcutsAndWidgets.requestLayout(); - } - return swapSolution.isSolution; - } - - int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, - View dragView, int[] result, int resultSpan[], int mode) { - // First we determine if things have moved enough to cause a different layout - result = findNearestArea(pixelX, pixelY, spanX, spanY, result); - - if (resultSpan == null) { - resultSpan = new int[2]; - } - - // When we are checking drop validity or actually dropping, we don't recompute the - // direction vector, since we want the solution to match the preview, and it's possible - // that the exact position of the item has changed to result in a new reordering outcome. - if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP) - && mPreviousReorderDirection[0] != INVALID_DIRECTION) { - mDirectionVector[0] = mPreviousReorderDirection[0]; - mDirectionVector[1] = mPreviousReorderDirection[1]; - // We reset this vector after drop - if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) { - mPreviousReorderDirection[0] = INVALID_DIRECTION; - mPreviousReorderDirection[1] = INVALID_DIRECTION; - } - } else { - getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector); - mPreviousReorderDirection[0] = mDirectionVector[0]; - mPreviousReorderDirection[1] = mDirectionVector[1]; - } - - ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY, - spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration()); - - // We attempt the approach which doesn't shuffle views at all - ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX, - minSpanY, spanX, spanY, dragView, new ItemConfiguration()); - - ItemConfiguration finalSolution = null; - if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) { - finalSolution = swapSolution; - } else if (noShuffleSolution.isSolution) { - finalSolution = noShuffleSolution; - } - - boolean foundSolution = true; - if (!DESTRUCTIVE_REORDER) { - setUseTempCoords(true); - } - - if (finalSolution != null) { - result[0] = finalSolution.dragViewX; - result[1] = finalSolution.dragViewY; - resultSpan[0] = finalSolution.dragViewSpanX; - resultSpan[1] = finalSolution.dragViewSpanY; - - // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother - // committing anything or animating anything as we just want to determine if a solution - // exists - if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) { - if (!DESTRUCTIVE_REORDER) { - copySolutionToTempState(finalSolution, dragView); - } - setItemPlacementDirty(true); - animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP); - - if (!DESTRUCTIVE_REORDER && - (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) { - commitTempPlacement(); - completeAndClearReorderHintAnimations(); - setItemPlacementDirty(false); - } else { - beginOrAdjustHintAnimations(finalSolution, dragView, - REORDER_ANIMATION_DURATION); - } - } - } else { - foundSolution = false; - result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1; - } - - if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) { - setUseTempCoords(false); - } - - mShortcutsAndWidgets.requestLayout(); - return result; - } - - void setItemPlacementDirty(boolean dirty) { - mItemPlacementDirty = dirty; - } - boolean isItemPlacementDirty() { - return mItemPlacementDirty; - } - - private class ItemConfiguration { - HashMap map = new HashMap(); - boolean isSolution = false; - int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY; - - int area() { - return dragViewSpanX * dragViewSpanY; - } - } - - private class CellAndSpan { - int x, y; - int spanX, spanY; - - public CellAndSpan(int x, int y, int spanX, int spanY) { - this.x = x; - this.y = y; - this.spanX = spanX; - this.spanY = spanY; - } - } - - /** - * 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 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, View ignoreView, int[] result) { - return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, 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 minSpanX The minimum horizontal span required - * @param minSpanY The minimum vertical span required - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @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 minSpanX, int minSpanY, - int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) { - return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true, - result, resultSpan, mOccupied); - } - - /** - * Find a starting cell position 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 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[] findNearestArea( - int pixelX, int pixelY, int spanX, int spanY, int[] result) { - return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result); - } - - boolean existsEmptyCell() { - return findCellForSpan(null, 1, 1); - } - - /** - * 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, mOccupied); - } - - /** - * 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, mOccupied); - } - - /** - * 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, mOccupied); - } - - /** - * The superset of the above two methods - */ - boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, - int intersectX, int intersectY, View ignoreView, boolean occupied[][]) { - // mark space take by ignoreView as available (method checks if ignoreView is null) - markCellsAsUnoccupiedForView(ignoreView, occupied); - - 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 y = startY; y < endY && !foundCell; y++) { - inner: - for (int x = startX; x < endX; x++) { - 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 after the column we just found - // an occupied cell - x += i; - 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, occupied); - return foundCell; - } - - /** - * 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 onDragEnter() { - mDragEnforcer.onDragEnter(); - mDragging = true; - } - - /** - * Called when drag has left this CellLayout or has been completed (successfully or not) - */ - void onDragExit() { - mDragEnforcer.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; - } - - // Invalidate the drag data - mDragCell[0] = mDragCell[1] = -1; - mDragOutlineAnims[mDragOutlineCurrent].animateOut(); - mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length; - revertTempState(); - setIsDragOverlapping(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 - */ - void onDropChild(View child) { - if (child != null) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.dropped = true; - child.requestLayout(); - } - } - - /** - * 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 cellVSpan Height in cells - * @param resultRect Rect into which to put the results - */ - public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) { - final int cellWidth = mCellWidth; - final int cellHeight = mCellHeight; - final int widthGap = mWidthGap; - final int heightGap = mHeightGap; - - final int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - - 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); - - resultRect.set(x, y, x + width, y + height); - } - - /** - * 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, 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. - int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width); - int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height); - int smallerSize = Math.min(actualWidth, actualHeight); - - // Always round up to next largest cell - int spanX = (int) Math.ceil(width / (float) smallerSize); - int spanY = (int) Math.ceil(height / (float) smallerSize); - - if (result == null) { - return new int[] { spanX, spanY }; - } - result[0] = spanX; - result[1] = spanY; - return result; - } - - public int[] cellSpansToSize(int hSpans, int vSpans) { - int[] size = new int[2]; - size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap; - size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap; - return size; - } - - /** - * Calculate the grid spans needed to fit given item - */ - public void calculateSpans(ItemInfo info) { - final int minWidth; - final int minHeight; - - if (info instanceof LauncherAppWidgetInfo) { - minWidth = ((LauncherAppWidgetInfo) info).minWidth; - minHeight = ((LauncherAppWidgetInfo) info).minHeight; - } else if (info instanceof PendingAddWidgetInfo) { - minWidth = ((PendingAddWidgetInfo) info).minWidth; - minHeight = ((PendingAddWidgetInfo) info).minHeight; - } else { - // It's not a widget, so it must be 1x1 - info.spanX = info.spanY = 1; - return; - } - int[] spans = rectToCell(minWidth, minHeight, null); - info.spanX = spans[0]; - info.spanY = spans[1]; - } - - /** - * Find the first vacant cell, if there is one. - * - * @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) { - - return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); - } - - static boolean findVacantCell(int[] vacant, int spanX, int spanY, - int xCount, int yCount, boolean[][] occupied) { - - for (int y = 0; y < yCount; y++) { - for (int x = 0; x < xCount; x++) { - boolean available = !occupied[x][y]; -out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { - for (int j = y; j < y + spanY - 1 && y < yCount; j++) { - available = available && !occupied[i][j]; - if (!available) break out; - } - } - - if (available) { - vacant[0] = x; - vacant[1] = y; - return true; - } - } - } - - return false; - } - - private void clearOccupiedCells() { - for (int x = 0; x < mCountX; x++) { - for (int y = 0; y < mCountY; y++) { - mOccupied[x][y] = false; - } - } - } - - public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) { - markCellsAsUnoccupiedForView(view); - markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true); - } - - public void markCellsAsOccupiedForView(View view) { - markCellsAsOccupiedForView(view, mOccupied); - } - public void markCellsAsOccupiedForView(View view, boolean[][] occupied) { - if (view == null || view.getParent() != mShortcutsAndWidgets) return; - LayoutParams lp = (LayoutParams) view.getLayoutParams(); - markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true); - } - - public void markCellsAsUnoccupiedForView(View view) { - markCellsAsUnoccupiedForView(view, mOccupied); - } - public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) { - if (view == null || view.getParent() != mShortcutsAndWidgets) return; - LayoutParams lp = (LayoutParams) view.getLayoutParams(); - markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false); - } - - private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied, - boolean value) { - if (cellX < 0 || cellY < 0) return; - for (int x = cellX; x < cellX + spanX && x < mCountX; x++) { - for (int y = cellY; y < cellY + spanY && y < mCountY; y++) { - occupied[x][y] = value; - } - } - } - - public int getDesiredWidth() { - return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) + - (Math.max((mCountX - 1), 0) * mWidthGap); - } - - public int getDesiredHeight() { - return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) + - (Math.max((mCountY - 1), 0) * mHeightGap); - } - - public boolean isOccupied(int x, int y) { - if (x < mCountX && y < mCountY) { - return mOccupied[x][y]; - } else { - throw new RuntimeException("Position exceeds the bound of this CellLayout"); - } - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new CellLayout.LayoutParams(getContext(), attrs); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof CellLayout.LayoutParams; - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - 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. - */ - @ViewDebug.ExportedProperty - public int cellX; - - /** - * Vertical location of the item in the grid. - */ - @ViewDebug.ExportedProperty - public int cellY; - - /** - * Temporary horizontal location of the item in the grid during reorder - */ - public int tmpCellX; - - /** - * Temporary vertical location of the item in the grid during reorder - */ - public int tmpCellY; - - /** - * Indicates that the temporary coordinates should be used to layout the items - */ - public boolean useTmpCoords; - - /** - * 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; - - /** - * Indicates whether the item will set its x, y, width and height parameters freely, - * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan. - */ - public boolean isLockedToGrid = true; - - /** - * Indicates whether this item can be reordered. Always true except in the case of the - * the AllApps button. - */ - public boolean canReorder = true; - - // X coordinate of the view in the layout. - @ViewDebug.ExportedProperty - int x; - // Y coordinate of the view in the layout. - @ViewDebug.ExportedProperty - int y; - - boolean dropped; - - 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) { - if (isLockedToGrid) { - final int myCellHSpan = cellHSpan; - final int myCellVSpan = cellVSpan; - final int myCellX = useTmpCoords ? tmpCellX : cellX; - final int myCellY = useTmpCoords ? tmpCellY : cellY; - - width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - - leftMargin - rightMargin; - height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - - topMargin - bottomMargin; - x = (int) (myCellX * (cellWidth + widthGap) + leftMargin); - y = (int) (myCellY * (cellHeight + heightGap) + topMargin); - } - } - - public String toString() { - return "(" + this.cellX + ", " + this.cellY + ")"; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getWidth() { - return width; - } - - public void setHeight(int height) { - this.height = height; - } - - public int getHeight() { - return height; - } - - public void setX(int x) { - this.x = x; - } - - public int getX() { - return x; - } - - public void setY(int y) { - this.y = y; - } - - public int getY() { - return y; - } - } - - // 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 { - View cell; - int cellX = -1; - int cellY = -1; - int spanX; - int spanY; - int screen; - long container; - - @Override - public String toString() { - return "Cell[view=" + (cell == null ? "null" : cell.getClass()) - + ", x=" + cellX + ", y=" + cellY + "]"; - } - } - - public boolean lastDownOnOccupiedCell() { - return mLastDownOnOccupiedCell; - } -} diff --git a/src/com/android/launcher2/CheckLongPressHelper.java b/src/com/android/launcher2/CheckLongPressHelper.java deleted file mode 100644 index 5c3752ad6..000000000 --- a/src/com/android/launcher2/CheckLongPressHelper.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2012 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.view.View; - -public class CheckLongPressHelper { - private View mView; - private boolean mHasPerformedLongPress; - private CheckForLongPress mPendingCheckForLongPress; - - class CheckForLongPress implements Runnable { - public void run() { - if ((mView.getParent() != null) && mView.hasWindowFocus() - && !mHasPerformedLongPress) { - if (mView.performLongClick()) { - mView.setPressed(false); - mHasPerformedLongPress = true; - } - } - } - } - - public CheckLongPressHelper(View v) { - mView = v; - } - - public void postCheckForLongPress() { - mHasPerformedLongPress = false; - - if (mPendingCheckForLongPress == null) { - mPendingCheckForLongPress = new CheckForLongPress(); - } - mView.postDelayed(mPendingCheckForLongPress, LauncherApplication.getLongPressTimeout()); - } - - public void cancelLongPress() { - mHasPerformedLongPress = false; - if (mPendingCheckForLongPress != null) { - mView.removeCallbacks(mPendingCheckForLongPress); - mPendingCheckForLongPress = null; - } - } - - public boolean hasPerformedLongPress() { - return mHasPerformedLongPress; - } -} diff --git a/src/com/android/launcher2/Cling.java b/src/com/android/launcher2/Cling.java deleted file mode 100644 index 646c54e90..000000000 --- a/src/com/android/launcher2/Cling.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -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.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.FocusFinder; -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityManager; -import android.widget.FrameLayout; - -import com.android.launcher.R; - -public class Cling extends FrameLayout { - - static final String WORKSPACE_CLING_DISMISSED_KEY = "cling.workspace.dismissed"; - static final String ALLAPPS_CLING_DISMISSED_KEY = "cling.allapps.dismissed"; - static final String FOLDER_CLING_DISMISSED_KEY = "cling.folder.dismissed"; - - private static String WORKSPACE_PORTRAIT = "workspace_portrait"; - private static String WORKSPACE_LANDSCAPE = "workspace_landscape"; - private static String WORKSPACE_LARGE = "workspace_large"; - private static String WORKSPACE_CUSTOM = "workspace_custom"; - - private static String ALLAPPS_PORTRAIT = "all_apps_portrait"; - private static String ALLAPPS_LANDSCAPE = "all_apps_landscape"; - private static String ALLAPPS_LARGE = "all_apps_large"; - - private static String FOLDER_PORTRAIT = "folder_portrait"; - private static String FOLDER_LANDSCAPE = "folder_landscape"; - private static String FOLDER_LARGE = "folder_large"; - - private Launcher mLauncher; - private boolean mIsInitialized; - private String mDrawIdentifier; - private Drawable mBackground; - private Drawable mPunchThroughGraphic; - private Drawable mHandTouchGraphic; - private int mPunchThroughGraphicCenterRadius; - private int mAppIconSize; - private int mButtonBarHeight; - private float mRevealRadius; - private int[] mPositionData; - - private Paint mErasePaint; - - public Cling(Context context) { - this(context, null, 0); - } - - public Cling(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public Cling(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0); - mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier); - a.recycle(); - } - - void init(Launcher l, int[] positionData) { - if (!mIsInitialized) { - mLauncher = l; - mPositionData = positionData; - - Resources r = getContext().getResources(); - - mPunchThroughGraphic = r.getDrawable(R.drawable.cling); - mPunchThroughGraphicCenterRadius = - r.getDimensionPixelSize(R.dimen.clingPunchThroughGraphicCenterRadius); - mAppIconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); - mRevealRadius = r.getDimensionPixelSize(R.dimen.reveal_radius) * 1f; - mButtonBarHeight = r.getDimensionPixelSize(R.dimen.button_bar_height); - - mErasePaint = new Paint(); - mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); - mErasePaint.setColor(0xFFFFFF); - mErasePaint.setAlpha(0); - - mIsInitialized = true; - } - } - - void cleanup() { - mBackground = null; - mPunchThroughGraphic = null; - mHandTouchGraphic = null; - mIsInitialized = false; - } - - public String getDrawIdentifier() { - return mDrawIdentifier; - } - - private int[] getPunchThroughPositions() { - if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)) { - return new int[]{getMeasuredWidth() / 2, getMeasuredHeight() - (mButtonBarHeight / 2)}; - } else if (mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)) { - return new int[]{getMeasuredWidth() - (mButtonBarHeight / 2), getMeasuredHeight() / 2}; - } else if (mDrawIdentifier.equals(WORKSPACE_LARGE)) { - final float scale = LauncherApplication.getScreenDensity(); - final int cornerXOffset = (int) (scale * 15); - final int cornerYOffset = (int) (scale * 10); - return new int[]{getMeasuredWidth() - cornerXOffset, cornerYOffset}; - } else if (mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || - mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || - mDrawIdentifier.equals(ALLAPPS_LARGE)) { - return mPositionData; - } - return new int[]{-1, -1}; - } - - @Override - public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) { - if (descendant.includeForAccessibility()) { - return descendant; - } - return null; - } - - @Override - public View focusSearch(int direction) { - return this.focusSearch(null, direction); - } - - @Override - public View focusSearch(View focused, int direction) { - return FocusFinder.getInstance().findNextFocus(this, focused, direction); - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) - || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) - || mDrawIdentifier.equals(WORKSPACE_LARGE) - || mDrawIdentifier.equals(ALLAPPS_PORTRAIT) - || mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) - || mDrawIdentifier.equals(ALLAPPS_LARGE) - || mDrawIdentifier.equals(WORKSPACE_CUSTOM)); - } - - @Override - public boolean onTouchEvent(android.view.MotionEvent event) { - if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || - mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || - mDrawIdentifier.equals(WORKSPACE_LARGE) || - mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || - mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || - mDrawIdentifier.equals(ALLAPPS_LARGE)) { - - int[] positions = getPunchThroughPositions(); - for (int i = 0; i < positions.length; i += 2) { - double diff = Math.sqrt(Math.pow(event.getX() - positions[i], 2) + - Math.pow(event.getY() - positions[i + 1], 2)); - if (diff < mRevealRadius) { - return false; - } - } - } else if (mDrawIdentifier.equals(FOLDER_PORTRAIT) || - mDrawIdentifier.equals(FOLDER_LANDSCAPE) || - mDrawIdentifier.equals(FOLDER_LARGE)) { - Folder f = mLauncher.getWorkspace().getOpenFolder(); - if (f != null) { - Rect r = new Rect(); - f.getHitRect(r); - if (r.contains((int) event.getX(), (int) event.getY())) { - return false; - } - } - } - return true; - }; - - @Override - protected void dispatchDraw(Canvas canvas) { - if (mIsInitialized) { - DisplayMetrics metrics = new DisplayMetrics(); - mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics); - - // Initialize the draw buffer (to allow punching through) - Bitmap b = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), - Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - - // Draw the background - if (mBackground == null) { - if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || - mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || - mDrawIdentifier.equals(WORKSPACE_LARGE)) { - mBackground = getResources().getDrawable(R.drawable.bg_cling1); - } else if (mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || - mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || - mDrawIdentifier.equals(ALLAPPS_LARGE)) { - mBackground = getResources().getDrawable(R.drawable.bg_cling2); - } else if (mDrawIdentifier.equals(FOLDER_PORTRAIT) || - mDrawIdentifier.equals(FOLDER_LANDSCAPE)) { - mBackground = getResources().getDrawable(R.drawable.bg_cling3); - } else if (mDrawIdentifier.equals(FOLDER_LARGE)) { - mBackground = getResources().getDrawable(R.drawable.bg_cling4); - } else if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) { - mBackground = getResources().getDrawable(R.drawable.bg_cling5); - } - } - if (mBackground != null) { - mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); - mBackground.draw(c); - } else { - c.drawColor(0x99000000); - } - - int cx = -1; - int cy = -1; - float scale = mRevealRadius / mPunchThroughGraphicCenterRadius; - int dw = (int) (scale * mPunchThroughGraphic.getIntrinsicWidth()); - int dh = (int) (scale * mPunchThroughGraphic.getIntrinsicHeight()); - - // Determine where to draw the punch through graphic - int[] positions = getPunchThroughPositions(); - for (int i = 0; i < positions.length; i += 2) { - cx = positions[i]; - cy = positions[i + 1]; - if (cx > -1 && cy > -1) { - c.drawCircle(cx, cy, mRevealRadius, mErasePaint); - mPunchThroughGraphic.setBounds(cx - dw/2, cy - dh/2, cx + dw/2, cy + dh/2); - mPunchThroughGraphic.draw(c); - } - } - - // Draw the hand graphic in All Apps - if (mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || - mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || - mDrawIdentifier.equals(ALLAPPS_LARGE)) { - if (mHandTouchGraphic == null) { - mHandTouchGraphic = getResources().getDrawable(R.drawable.hand); - } - int offset = mAppIconSize / 4; - mHandTouchGraphic.setBounds(cx + offset, cy + offset, - cx + mHandTouchGraphic.getIntrinsicWidth() + offset, - cy + mHandTouchGraphic.getIntrinsicHeight() + offset); - mHandTouchGraphic.draw(c); - } - - canvas.drawBitmap(b, 0, 0, null); - c.setBitmap(null); - b = null; - } - - // Draw the rest of the cling - super.dispatchDraw(canvas); - }; -} diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java deleted file mode 100644 index 930da56aa..000000000 --- a/src/com/android/launcher2/DeferredHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import java.util.LinkedList; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.MessageQueue; - -/** - * Queue of things to run on a looper thread. Items posted with {@link #post} will not - * be actually enqued on the handler until after the last one has run, to keep from - * starving the thread. - * - * This class is fifo. - */ -public class DeferredHandler { - private LinkedList mQueue = new LinkedList(); - private MessageQueue mMessageQueue = Looper.myQueue(); - private Impl mHandler = new Impl(); - - private class Impl extends Handler implements MessageQueue.IdleHandler { - public void handleMessage(Message msg) { - Runnable r; - synchronized (mQueue) { - if (mQueue.size() == 0) { - return; - } - r = mQueue.removeFirst(); - } - r.run(); - synchronized (mQueue) { - scheduleNextLocked(); - } - } - - public boolean queueIdle() { - handleMessage(null); - return false; - } - } - - private class IdleRunnable implements Runnable { - Runnable mRunnable; - - IdleRunnable(Runnable r) { - mRunnable = r; - } - - public void run() { - mRunnable.run(); - } - } - - public DeferredHandler() { - } - - /** Schedule runnable to run after everything that's on the queue right now. */ - public void post(Runnable runnable) { - synchronized (mQueue) { - mQueue.add(runnable); - if (mQueue.size() == 1) { - scheduleNextLocked(); - } - } - } - - /** Schedule runnable to run when the queue goes idle. */ - public void postIdle(final Runnable runnable) { - post(new IdleRunnable(runnable)); - } - - public void cancelRunnable(Runnable runnable) { - synchronized (mQueue) { - while (mQueue.remove(runnable)) { } - } - } - - public void cancel() { - synchronized (mQueue) { - mQueue.clear(); - } - } - - void scheduleNextLocked() { - if (mQueue.size() > 0) { - Runnable peek = mQueue.getFirst(); - if (peek instanceof IdleRunnable) { - mMessageQueue.addIdleHandler(mHandler); - } else { - mHandler.sendEmptyMessage(1); - } - } - } -} - diff --git a/src/com/android/launcher2/DeleteDropTarget.java b/src/com/android/launcher2/DeleteDropTarget.java deleted file mode 100644 index 949c035ea..000000000 --- a/src/com/android/launcher2/DeleteDropTarget.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; - -import com.android.launcher.R; - -public class DeleteDropTarget extends ButtonDropTarget { - private static int DELETE_ANIMATION_DURATION = 285; - private static int FLING_DELETE_ANIMATION_DURATION = 350; - private static float FLING_TO_DELETE_FRICTION = 0.035f; - private static int MODE_FLING_DELETE_TO_TRASH = 0; - private static int MODE_FLING_DELETE_ALONG_VECTOR = 1; - - private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR; - - private ColorStateList mOriginalTextColor; - private TransitionDrawable mUninstallDrawable; - private TransitionDrawable mRemoveDrawable; - private TransitionDrawable mCurrentDrawable; - - public DeleteDropTarget(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - // Get the drawable - mOriginalTextColor = getTextColors(); - - // Get the hover color - Resources r = getResources(); - mHoverColor = r.getColor(R.color.delete_target_hover_tint); - mUninstallDrawable = (TransitionDrawable) - r.getDrawable(R.drawable.uninstall_target_selector); - mRemoveDrawable = (TransitionDrawable) r.getDrawable(R.drawable.remove_target_selector); - - mRemoveDrawable.setCrossFadeEnabled(true); - mUninstallDrawable.setCrossFadeEnabled(true); - - // The current drawable is set to either the remove drawable or the uninstall drawable - // and is initially set to the remove drawable, as set in the layout xml. - mCurrentDrawable = (TransitionDrawable) getCurrentDrawable(); - - // Remove the text in the Phone UI in landscape - int orientation = getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - if (!LauncherApplication.isScreenLarge()) { - setText(""); - } - } - } - - private boolean isAllAppsApplication(DragSource source, Object info) { - return (source instanceof AppsCustomizePagedView) && (info instanceof ApplicationInfo); - } - private boolean isAllAppsWidget(DragSource source, Object info) { - if (source instanceof AppsCustomizePagedView) { - if (info instanceof PendingAddItemInfo) { - PendingAddItemInfo addInfo = (PendingAddItemInfo) info; - switch (addInfo.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - return true; - } - } - } - return false; - } - private boolean isDragSourceWorkspaceOrFolder(DragObject d) { - return (d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder); - } - private boolean isWorkspaceOrFolderApplication(DragObject d) { - return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof ShortcutInfo); - } - private boolean isWorkspaceOrFolderWidget(DragObject d) { - return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof LauncherAppWidgetInfo); - } - private boolean isWorkspaceFolder(DragObject d) { - return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo); - } - - private void setHoverColor() { - mCurrentDrawable.startTransition(mTransitionDuration); - setTextColor(mHoverColor); - } - private void resetHoverColor() { - mCurrentDrawable.resetTransition(); - setTextColor(mOriginalTextColor); - } - - @Override - public boolean acceptDrop(DragObject d) { - // We can remove everything including App shortcuts, folders, widgets, etc. - return true; - } - - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - boolean isVisible = true; - boolean isUninstall = false; - - // If we are dragging a widget from AppsCustomize, hide the delete target - if (isAllAppsWidget(source, info)) { - isVisible = false; - } - - // If we are dragging an application from AppsCustomize, only show the control if we can - // delete the app (it was downloaded), and rename the string to "uninstall" in such a case - if (isAllAppsApplication(source, info)) { - ApplicationInfo appInfo = (ApplicationInfo) info; - if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) != 0) { - isUninstall = true; - } else { - isVisible = false; - } - } - - if (isUninstall) { - setCompoundDrawablesWithIntrinsicBounds(mUninstallDrawable, null, null, null); - } else { - setCompoundDrawablesWithIntrinsicBounds(mRemoveDrawable, null, null, null); - } - mCurrentDrawable = (TransitionDrawable) getCurrentDrawable(); - - mActive = isVisible; - resetHoverColor(); - ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE); - if (getText().length() > 0) { - setText(isUninstall ? R.string.delete_target_uninstall_label - : R.string.delete_target_label); - } - } - - @Override - public void onDragEnd() { - super.onDragEnd(); - mActive = false; - } - - public void onDragEnter(DragObject d) { - super.onDragEnter(d); - - setHoverColor(); - } - - public void onDragExit(DragObject d) { - super.onDragExit(d); - - if (!d.dragComplete) { - resetHoverColor(); - } else { - // Restore the hover color if we are deleting - d.dragView.setColor(mHoverColor); - } - } - - private void animateToTrashAndCompleteDrop(final DragObject d) { - DragLayer dragLayer = mLauncher.getDragLayer(); - Rect from = new Rect(); - dragLayer.getViewRectRelativeToSelf(d.dragView, from); - Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), - mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight()); - float scale = (float) to.width() / from.width(); - - mSearchDropTargetBar.deferOnDragEnd(); - Runnable onAnimationEndRunnable = new Runnable() { - @Override - public void run() { - mSearchDropTargetBar.onDragEnd(); - mLauncher.exitSpringLoadedDragMode(); - completeDrop(d); - } - }; - dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f, - DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2), - new LinearInterpolator(), onAnimationEndRunnable, - DragLayer.ANIMATION_END_DISAPPEAR, null); - } - - private void completeDrop(DragObject d) { - ItemInfo item = (ItemInfo) d.dragInfo; - - if (isAllAppsApplication(d.dragSource, item)) { - // Uninstall the application if it is being dragged from AppsCustomize - mLauncher.startApplicationUninstallActivity((ApplicationInfo) item); - } else if (isWorkspaceOrFolderApplication(d)) { - LauncherModel.deleteItemFromDatabase(mLauncher, item); - } else if (isWorkspaceFolder(d)) { - // Remove the folder from the workspace and delete the contents from launcher model - FolderInfo folderInfo = (FolderInfo) item; - mLauncher.removeFolder(folderInfo); - LauncherModel.deleteFolderContentsFromDatabase(mLauncher, folderInfo); - } else if (isWorkspaceOrFolderWidget(d)) { - // Remove the widget from the workspace - mLauncher.removeAppWidget((LauncherAppWidgetInfo) item); - LauncherModel.deleteItemFromDatabase(mLauncher, item); - - final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item; - final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost(); - if (appWidgetHost != null) { - // Deleting an app widget ID is a void call but writes to disk before returning - // to the caller... - new Thread("deleteAppWidgetId") { - public void run() { - appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId); - } - }.start(); - } - } - } - - public void onDrop(DragObject d) { - animateToTrashAndCompleteDrop(d); - } - - /** - * Creates an animation from the current drag view to the delete trash icon. - */ - private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer, - DragObject d, PointF vel, ViewConfiguration config) { - final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), - mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight()); - final Rect from = new Rect(); - dragLayer.getViewRectRelativeToSelf(d.dragView, from); - - // Calculate how far along the velocity vector we should put the intermediate point on - // the bezier curve - float velocity = Math.abs(vel.length()); - float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f)); - int offsetY = (int) (-from.top * vp); - int offsetX = (int) (offsetY / (vel.y / vel.x)); - final float y2 = from.top + offsetY; // intermediate t/l - final float x2 = from.left + offsetX; - final float x1 = from.left; // drag view t/l - final float y1 = from.top; - final float x3 = to.left; // delete target t/l - final float y3 = to.top; - - final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() { - @Override - public float getInterpolation(float t) { - return t * t * t * t * t * t * t * t; - } - }; - return new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final DragView dragView = (DragView) dragLayer.getAnimatedView(); - float t = ((Float) animation.getAnimatedValue()).floatValue(); - float tp = scaleAlphaInterpolator.getInterpolation(t); - float initialScale = dragView.getInitialScale(); - float finalAlpha = 0.5f; - float scale = dragView.getScaleX(); - float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f; - float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f; - float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) + - (t * t) * x3; - float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) + - (t * t) * y3; - - dragView.setTranslationX(x); - dragView.setTranslationY(y); - dragView.setScaleX(initialScale * (1f - tp)); - dragView.setScaleY(initialScale * (1f - tp)); - dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp)); - } - }; - } - - /** - * Creates an animation from the current drag view along its current velocity vector. - * For this animation, the alpha runs for a fixed duration and we update the position - * progressively. - */ - private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener { - private DragLayer mDragLayer; - private PointF mVelocity; - private Rect mFrom; - private long mPrevTime; - private boolean mHasOffsetForScale; - private float mFriction; - - private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f); - - public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from, - long startTime, float friction) { - mDragLayer = dragLayer; - mVelocity = vel; - mFrom = from; - mPrevTime = startTime; - mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction); - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final DragView dragView = (DragView) mDragLayer.getAnimatedView(); - float t = ((Float) animation.getAnimatedValue()).floatValue(); - long curTime = AnimationUtils.currentAnimationTimeMillis(); - - if (!mHasOffsetForScale) { - mHasOffsetForScale = true; - float scale = dragView.getScaleX(); - float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f; - float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f; - - mFrom.left += xOffset; - mFrom.top += yOffset; - } - - mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f); - mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f); - - dragView.setTranslationX(mFrom.left); - dragView.setTranslationY(mFrom.top); - dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t)); - - mVelocity.x *= mFriction; - mVelocity.y *= mFriction; - mPrevTime = curTime; - } - }; - private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer, - DragObject d, PointF vel, final long startTime, final int duration, - ViewConfiguration config) { - final Rect from = new Rect(); - dragLayer.getViewRectRelativeToSelf(d.dragView, from); - - return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime, - FLING_TO_DELETE_FRICTION); - } - - public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) { - final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView; - - // Don't highlight the icon as it's animating - d.dragView.setColor(0); - d.dragView.updateInitialScaleToCurrentScale(); - // Don't highlight the target if we are flinging from AllApps - if (isAllApps) { - resetHoverColor(); - } - - if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) { - // Defer animating out the drop target if we are animating to it - mSearchDropTargetBar.deferOnDragEnd(); - mSearchDropTargetBar.finishAnimations(); - } - - final ViewConfiguration config = ViewConfiguration.get(mLauncher); - final DragLayer dragLayer = mLauncher.getDragLayer(); - final int duration = FLING_DELETE_ANIMATION_DURATION; - final long startTime = AnimationUtils.currentAnimationTimeMillis(); - - // NOTE: Because it takes time for the first frame of animation to actually be - // called and we expect the animation to be a continuation of the fling, we have - // to account for the time that has elapsed since the fling finished. And since - // we don't have a startDelay, we will always get call to update when we call - // start() (which we want to ignore). - final TimeInterpolator tInterpolator = new TimeInterpolator() { - private int mCount = -1; - private float mOffset = 0f; - - @Override - public float getInterpolation(float t) { - if (mCount < 0) { - mCount++; - } else if (mCount == 0) { - mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() - - startTime) / duration); - mCount++; - } - return Math.min(1f, mOffset + t); - } - }; - AnimatorUpdateListener updateCb = null; - if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) { - updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config); - } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) { - updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime, - duration, config); - } - Runnable onAnimationEndRunnable = new Runnable() { - @Override - public void run() { - mSearchDropTargetBar.onDragEnd(); - - // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up - // itself, otherwise, complete the drop to initiate the deletion process - if (!isAllApps) { - mLauncher.exitSpringLoadedDragMode(); - completeDrop(d); - } - mLauncher.getDragController().onDeferredEndFling(d); - } - }; - dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable, - DragLayer.ANIMATION_END_DISAPPEAR, null); - } -} diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java deleted file mode 100644 index 84f151581..000000000 --- a/src/com/android/launcher2/DragController.java +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Handler; -import android.os.IBinder; -import android.os.Vibrator; -import android.util.Log; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.inputmethod.InputMethodManager; - -import com.android.launcher.R; - -import java.util.ArrayList; - -/** - * Class for initiating a drag within a view or across multiple views. - */ -public class DragController { - private static final String TAG = "Launcher.DragController"; - - /** Indicates the drag is a move. */ - public static int DRAG_ACTION_MOVE = 0; - - /** Indicates the drag is a copy. */ - public static int DRAG_ACTION_COPY = 1; - - private static final int SCROLL_DELAY = 500; - private static final int RESCROLL_DELAY = 750; - private static final int VIBRATE_DURATION = 15; - - private static final boolean PROFILE_DRAWING_DURING_DRAG = false; - - private static final int SCROLL_OUTSIDE_ZONE = 0; - private static final int SCROLL_WAITING_IN_ZONE = 1; - - static final int SCROLL_NONE = -1; - static final int SCROLL_LEFT = 0; - static final int SCROLL_RIGHT = 1; - - private static final float MAX_FLING_DEGREES = 35f; - - private Launcher mLauncher; - private Handler mHandler; - private final Vibrator mVibrator; - - // temporaries to avoid gc thrash - private Rect mRectTemp = new Rect(); - private final int[] mCoordinatesTemp = new int[2]; - - /** Whether or not we're dragging. */ - private boolean mDragging; - - /** X coordinate of the down event. */ - private int mMotionDownX; - - /** Y coordinate of the down event. */ - private int mMotionDownY; - - /** the area at the edge of the screen that makes the workspace go left - * or right while you're dragging. - */ - private int mScrollZone; - - private DropTarget.DragObject mDragObject; - - /** Who can receive drop events */ - private ArrayList mDropTargets = new ArrayList(); - private ArrayList mListeners = new ArrayList(); - private DropTarget mFlingToDeleteDropTarget; - - /** The window token used as the parent for the DragView. */ - private IBinder mWindowToken; - - /** The view that will be scrolled when dragging to the left and right edges of the screen. */ - private View mScrollView; - - private View mMoveTarget; - - private DragScroller mDragScroller; - private int mScrollState = SCROLL_OUTSIDE_ZONE; - private ScrollRunnable mScrollRunnable = new ScrollRunnable(); - - private DropTarget mLastDropTarget; - - private InputMethodManager mInputMethodManager; - - private int mLastTouch[] = new int[2]; - private long mLastTouchUpTime = -1; - private int mDistanceSinceScroll = 0; - - private int mTmpPoint[] = new int[2]; - private Rect mDragLayerRect = new Rect(); - - protected int mFlingToDeleteThresholdVelocity; - private VelocityTracker mVelocityTracker; - - /** - * Interface to receive notifications when a drag starts or stops - */ - interface DragListener { - - /** - * A drag has begun - * - * @param source An object representing where the drag originated - * @param info The data associated with the object that is being dragged - * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} - * or {@link DragController#DRAG_ACTION_COPY} - */ - void onDragStart(DragSource source, Object info, int dragAction); - - /** - * The drag has ended - */ - void onDragEnd(); - } - - /** - * Used to create a new DragLayer from XML. - * - * @param context The application's context. - */ - public DragController(Launcher launcher) { - Resources r = launcher.getResources(); - mLauncher = launcher; - mHandler = new Handler(); - mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone); - mVelocityTracker = VelocityTracker.obtain(); - mVibrator = (Vibrator) launcher.getSystemService(Context.VIBRATOR_SERVICE); - - float density = r.getDisplayMetrics().density; - mFlingToDeleteThresholdVelocity = - (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density); - } - - public boolean dragging() { - return mDragging; - } - - /** - * 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, float initialDragViewScale) { - int[] loc = mCoordinatesTemp; - mLauncher.getDragLayer().getLocationInDragLayer(v, loc); - int dragLayerX = loc[0] + v.getPaddingLeft() + - (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); - int dragLayerY = loc[1] + v.getPaddingTop() + - (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); - - startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, - initialDragViewScale); - - 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 dragLayerX The x position in the DragLayer of the left-top of the bitmap. - * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. - * @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 dragLayerX, int dragLayerY, - DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, - float initialDragViewScale) { - if (PROFILE_DRAWING_DURING_DRAG) { - android.os.Debug.startMethodTracing("Launcher"); - } - - // Hide soft keyboard, if visible - if (mInputMethodManager == null) { - mInputMethodManager = (InputMethodManager) - mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); - } - mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); - - for (DragListener listener : mListeners) { - listener.onDragStart(source, dragInfo, dragAction); - } - - final int registrationX = mMotionDownX - dragLayerX; - final int registrationY = mMotionDownY - dragLayerY; - - final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; - final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; - - mDragging = true; - - mDragObject = new DropTarget.DragObject(); - - mDragObject.dragComplete = false; - mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); - mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); - mDragObject.dragSource = source; - mDragObject.dragInfo = dragInfo; - - mVibrator.vibrate(VIBRATE_DURATION); - - final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, - registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); - - if (dragOffset != null) { - dragView.setDragVisualizeOffset(new Point(dragOffset)); - } - if (dragRegion != null) { - dragView.setDragRegion(new Rect(dragRegion)); - } - - dragView.show(mMotionDownX, mMotionDownY); - handleMoveEvent(mMotionDownX, mMotionDownY); - } - - /** - * Draw the view into a bitmap. - */ - Bitmap getViewBitmap(View v) { - v.clearFocus(); - v.setPressed(false); - - boolean willNotCache = v.willNotCacheDrawing(); - v.setWillNotCacheDrawing(false); - - // Reset the drawing cache background color to fully transparent - // for the duration of this operation - int color = v.getDrawingCacheBackgroundColor(); - v.setDrawingCacheBackgroundColor(0); - float alpha = v.getAlpha(); - v.setAlpha(1.0f); - - if (color != 0) { - v.destroyDrawingCache(); - } - v.buildDrawingCache(); - Bitmap cacheBitmap = v.getDrawingCache(); - if (cacheBitmap == null) { - Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); - return null; - } - - Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); - - // Restore the view - v.destroyDrawingCache(); - v.setAlpha(alpha); - v.setWillNotCacheDrawing(willNotCache); - v.setDrawingCacheBackgroundColor(color); - - return bitmap; - } - - /** - * Call this from a drag source view like this: - * - *
-     *  @Override
-     *  public boolean dispatchKeyEvent(KeyEvent event) {
-     *      return mDragController.dispatchKeyEvent(this, event)
-     *              || super.dispatchKeyEvent(event);
-     * 
- */ - public boolean dispatchKeyEvent(KeyEvent event) { - return mDragging; - } - - public boolean isDragging() { - return mDragging; - } - - /** - * Stop dragging without dropping. - */ - public void cancelDrag() { - if (mDragging) { - if (mLastDropTarget != null) { - mLastDropTarget.onDragExit(mDragObject); - } - mDragObject.deferDragViewCleanupPostAnimation = false; - mDragObject.cancelled = true; - mDragObject.dragComplete = true; - mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); - } - endDrag(); - } - public void onAppsRemoved(ArrayList apps, Context context) { - // Cancel the current drag if we are removing an app that we are dragging - if (mDragObject != null) { - Object rawDragInfo = mDragObject.dragInfo; - if (rawDragInfo instanceof ShortcutInfo) { - ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; - for (ApplicationInfo info : apps) { - // Added null checks to prevent NPE we've seen in the wild - if (dragInfo != null && - dragInfo.intent != null && - info.intent != null) { - boolean isSamePackage = dragInfo.getPackageName().equals( - info.getPackageName()); - if (isSamePackage) { - cancelDrag(); - return; - } - } - } - } - } - } - - private void endDrag() { - if (mDragging) { - mDragging = false; - clearScrollRunnable(); - boolean isDeferred = false; - if (mDragObject.dragView != null) { - isDeferred = mDragObject.deferDragViewCleanupPostAnimation; - if (!isDeferred) { - mDragObject.dragView.remove(); - } - mDragObject.dragView = null; - } - - // Only end the drag if we are not deferred - if (!isDeferred) { - for (DragListener listener : mListeners) { - listener.onDragEnd(); - } - } - } - - releaseVelocityTracker(); - } - - /** - * This only gets called as a result of drag view cleanup being deferred in endDrag(); - */ - void onDeferredEndDrag(DragView dragView) { - dragView.remove(); - - // If we skipped calling onDragEnd() before, do it now - for (DragListener listener : mListeners) { - listener.onDragEnd(); - } - } - - void onDeferredEndFling(DropTarget.DragObject d) { - d.dragSource.onFlingToDeleteCompleted(); - } - - /** - * Clamps the position to the drag layer bounds. - */ - private int[] getClampedDragLayerPos(float x, float y) { - mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); - mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); - mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); - return mTmpPoint; - } - - long getLastGestureUpTime() { - if (mDragging) { - return System.currentTimeMillis(); - } else { - return mLastTouchUpTime; - } - } - - void resetLastGestureUpTime() { - mLastTouchUpTime = -1; - } - - /** - * Call this from a drag source view. - */ - public boolean onInterceptTouchEvent(MotionEvent ev) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" - + mDragging); - } - - // Update the velocity tracker - acquireVelocityTrackerAndAddMovement(ev); - - final int action = ev.getAction(); - final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); - final int dragLayerX = dragLayerPos[0]; - final int dragLayerY = dragLayerPos[1]; - - switch (action) { - case MotionEvent.ACTION_MOVE: - break; - case MotionEvent.ACTION_DOWN: - // Remember location of down touch - mMotionDownX = dragLayerX; - mMotionDownY = dragLayerY; - mLastDropTarget = null; - break; - case MotionEvent.ACTION_UP: - mLastTouchUpTime = System.currentTimeMillis(); - if (mDragging) { - PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (vec != null) { - dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); - } else { - drop(dragLayerX, dragLayerY); - } - } - endDrag(); - break; - case MotionEvent.ACTION_CANCEL: - cancelDrag(); - break; - } - - return mDragging; - } - - /** - * Sets the view that should handle move events. - */ - void setMoveTarget(View view) { - mMoveTarget = view; - } - - public boolean dispatchUnhandledMove(View focused, int direction) { - return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); - } - - private void clearScrollRunnable() { - mHandler.removeCallbacks(mScrollRunnable); - if (mScrollState == SCROLL_WAITING_IN_ZONE) { - mScrollState = SCROLL_OUTSIDE_ZONE; - mScrollRunnable.setDirection(SCROLL_RIGHT); - mDragScroller.onExitScrollArea(); - mLauncher.getDragLayer().onExitScrollArea(); - } - } - - private void handleMoveEvent(int x, int y) { - mDragObject.dragView.move(x, y); - - // Drop on someone? - final int[] coordinates = mCoordinatesTemp; - DropTarget dropTarget = findDropTarget(x, y, coordinates); - mDragObject.x = coordinates[0]; - mDragObject.y = coordinates[1]; - if (dropTarget != null) { - DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); - if (delegate != null) { - dropTarget = delegate; - } - - if (mLastDropTarget != dropTarget) { - if (mLastDropTarget != null) { - mLastDropTarget.onDragExit(mDragObject); - } - dropTarget.onDragEnter(mDragObject); - } - dropTarget.onDragOver(mDragObject); - } else { - if (mLastDropTarget != null) { - mLastDropTarget.onDragExit(mDragObject); - } - } - mLastDropTarget = dropTarget; - - // 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(mLauncher).getScaledWindowTouchSlop(); - mDistanceSinceScroll += - Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); - mLastTouch[0] = x; - mLastTouch[1] = y; - final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; - - if (x < mScrollZone) { - if (mScrollState == SCROLL_OUTSIDE_ZONE) { - mScrollState = SCROLL_WAITING_IN_ZONE; - if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) { - mLauncher.getDragLayer().onEnterScrollArea(SCROLL_LEFT); - mScrollRunnable.setDirection(SCROLL_LEFT); - mHandler.postDelayed(mScrollRunnable, delay); - } - } - } else if (x > mScrollView.getWidth() - mScrollZone) { - if (mScrollState == SCROLL_OUTSIDE_ZONE) { - mScrollState = SCROLL_WAITING_IN_ZONE; - if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) { - mLauncher.getDragLayer().onEnterScrollArea(SCROLL_RIGHT); - mScrollRunnable.setDirection(SCROLL_RIGHT); - mHandler.postDelayed(mScrollRunnable, delay); - } - } - } else { - clearScrollRunnable(); - } - } - - public void forceMoveEvent() { - if (mDragging) { - handleMoveEvent(mDragObject.x, mDragObject.y); - } - } - - /** - * Call this from a drag source view. - */ - public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging) { - return false; - } - - // Update the velocity tracker - acquireVelocityTrackerAndAddMovement(ev); - - final int action = ev.getAction(); - final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); - final int dragLayerX = dragLayerPos[0]; - final int dragLayerY = dragLayerPos[1]; - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Remember where the motion event started - mMotionDownX = dragLayerX; - mMotionDownY = dragLayerY; - - if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { - mScrollState = SCROLL_WAITING_IN_ZONE; - mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); - } else { - mScrollState = SCROLL_OUTSIDE_ZONE; - } - break; - case MotionEvent.ACTION_MOVE: - handleMoveEvent(dragLayerX, dragLayerY); - break; - case MotionEvent.ACTION_UP: - // Ensure that we've processed a move event at the current pointer location. - handleMoveEvent(dragLayerX, dragLayerY); - mHandler.removeCallbacks(mScrollRunnable); - - if (mDragging) { - PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (vec != null) { - dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); - } else { - drop(dragLayerX, dragLayerY); - } - } - endDrag(); - break; - case MotionEvent.ACTION_CANCEL: - mHandler.removeCallbacks(mScrollRunnable); - cancelDrag(); - break; - } - - return true; - } - - /** - * Determines whether the user flung the current item to delete it. - * - * @return the vector at which the item was flung, or null if no fling was detected. - */ - private PointF isFlingingToDelete(DragSource source) { - if (mFlingToDeleteDropTarget == null) return null; - if (!source.supportsFlingToDelete()) return null; - - ViewConfiguration config = ViewConfiguration.get(mLauncher); - mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); - - if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { - // Do a quick dot product test to ensure that we are flinging upwards - PointF vel = new PointF(mVelocityTracker.getXVelocity(), - mVelocityTracker.getYVelocity()); - PointF upVec = new PointF(0f, -1f); - float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / - (vel.length() * upVec.length())); - if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { - return vel; - } - } - return null; - } - - private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { - final int[] coordinates = mCoordinatesTemp; - - mDragObject.x = coordinates[0]; - mDragObject.y = coordinates[1]; - - // Clean up dragging on the target if it's not the current fling delete target otherwise, - // start dragging to it. - if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { - mLastDropTarget.onDragExit(mDragObject); - } - - // Drop onto the fling-to-delete target - boolean accepted = false; - mFlingToDeleteDropTarget.onDragEnter(mDragObject); - // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for - // "drop" - mDragObject.dragComplete = true; - mFlingToDeleteDropTarget.onDragExit(mDragObject); - if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { - mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y, - vel); - accepted = true; - } - mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, - accepted); - } - - private void drop(float x, float y) { - final int[] coordinates = mCoordinatesTemp; - final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); - - mDragObject.x = coordinates[0]; - mDragObject.y = coordinates[1]; - boolean accepted = false; - if (dropTarget != null) { - mDragObject.dragComplete = true; - dropTarget.onDragExit(mDragObject); - if (dropTarget.acceptDrop(mDragObject)) { - dropTarget.onDrop(mDragObject); - accepted = true; - } - } - mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted); - } - - private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { - final Rect r = mRectTemp; - - final ArrayList dropTargets = mDropTargets; - final int count = dropTargets.size(); - for (int i=count-1; i>=0; i--) { - DropTarget target = dropTargets.get(i); - if (!target.isDropEnabled()) - continue; - - target.getHitRect(r); - - // Convert the hit rect to DragLayer coordinates - target.getLocationInDragLayer(dropCoordinates); - r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); - - mDragObject.x = x; - mDragObject.y = y; - if (r.contains(x, y)) { - DropTarget delegate = target.getDropTargetDelegate(mDragObject); - if (delegate != null) { - target = delegate; - target.getLocationInDragLayer(dropCoordinates); - } - - // Make dropCoordinates relative to the DropTarget - dropCoordinates[0] = x - dropCoordinates[0]; - dropCoordinates[1] = y - dropCoordinates[1]; - - return target; - } - } - return null; - } - - public void setDragScoller(DragScroller scroller) { - mDragScroller = scroller; - } - - public void setWindowToken(IBinder token) { - mWindowToken = token; - } - - /** - * Sets the drag listner which will be notified when a drag starts or ends. - */ - public void addDragListener(DragListener l) { - mListeners.add(l); - } - - /** - * Remove a previously installed drag listener. - */ - public void removeDragListener(DragListener l) { - mListeners.remove(l); - } - - /** - * Add a DropTarget to the list of potential places to receive drop events. - */ - public void addDropTarget(DropTarget target) { - mDropTargets.add(target); - } - - /** - * Don't send drop events to target any more. - */ - public void removeDropTarget(DropTarget target) { - mDropTargets.remove(target); - } - - /** - * Sets the current fling-to-delete drop target. - */ - public void setFlingToDeleteDropTarget(DropTarget target) { - mFlingToDeleteDropTarget = target; - } - - private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - } - - private void releaseVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - /** - * Set which view scrolls for touch events near the edge of the screen. - */ - public void setScrollView(View v) { - mScrollView = v; - } - - DragView getDragView() { - return mDragObject.dragView; - } - - private class ScrollRunnable implements Runnable { - private int mDirection; - - ScrollRunnable() { - } - - public void run() { - if (mDragScroller != null) { - if (mDirection == SCROLL_LEFT) { - mDragScroller.scrollLeft(); - } else { - mDragScroller.scrollRight(); - } - mScrollState = SCROLL_OUTSIDE_ZONE; - mDistanceSinceScroll = 0; - mDragScroller.onExitScrollArea(); - mLauncher.getDragLayer().onExitScrollArea(); - - if (isDragging()) { - // Force an update so that we can requeue the scroller if necessary - forceMoveEvent(); - } - } - } - - void setDirection(int direction) { - mDirection = direction; - } - } -} diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java deleted file mode 100644 index 4be1914e0..000000000 --- a/src/com/android/launcher2/DragLayer.java +++ /dev/null @@ -1,766 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.android.launcher.R; - -import java.util.ArrayList; - -/** - * A ViewGroup that coordinates dragging across its descendants - */ -public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener { - private DragController mDragController; - private int[] mTmpXY = new int[2]; - - private int mXDown, mYDown; - private Launcher mLauncher; - - // Variables relating to resizing widgets - private final ArrayList mResizeFrames = - new ArrayList(); - private AppWidgetResizeFrame mCurrentResizeFrame; - - // Variables relating to animation of views after drop - private ValueAnimator mDropAnim = null; - private ValueAnimator mFadeOutAnim = null; - private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); - private DragView mDropView = null; - private int mAnchorViewInitialScrollX = 0; - private View mAnchorView = null; - - private boolean mHoverPointClosesFolder = false; - private Rect mHitRect = new Rect(); - private int mWorkspaceIndex = -1; - private int mQsbIndex = -1; - public static final int ANIMATION_END_DISAPPEAR = 0; - public static final int ANIMATION_END_FADE_OUT = 1; - public static final int ANIMATION_END_REMAIN_VISIBLE = 2; - - /** - * Used to create a new DragLayer from XML. - * - * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization values. - */ - public DragLayer(Context context, AttributeSet attrs) { - super(context, attrs); - - // Disable multitouch across the workspace/all apps/customize tray - setMotionEventSplittingEnabled(false); - setChildrenDrawingOrderEnabled(true); - setOnHierarchyChangeListener(this); - - mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo); - mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo); - } - - public void setup(Launcher launcher, DragController controller) { - mLauncher = launcher; - mDragController = controller; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); - } - - private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { - getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect); - if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { - return true; - } - return false; - } - - private boolean isEventOverFolder(Folder folder, MotionEvent ev) { - getDescendantRectRelativeToSelf(folder, mHitRect); - if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { - return true; - } - return false; - } - - private boolean handleTouchDown(MotionEvent ev, boolean intercept) { - Rect hitRect = new Rect(); - int x = (int) ev.getX(); - int y = (int) ev.getY(); - - for (AppWidgetResizeFrame child: mResizeFrames) { - child.getHitRect(hitRect); - if (hitRect.contains(x, y)) { - if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { - mCurrentResizeFrame = child; - mXDown = x; - mYDown = y; - requestDisallowInterceptTouchEvent(true); - return true; - } - } - } - - Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); - if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) { - if (currentFolder.isEditingName()) { - if (!isEventOverFolderTextRegion(currentFolder, ev)) { - currentFolder.dismissEditingName(); - return true; - } - } - - getDescendantRectRelativeToSelf(currentFolder, hitRect); - if (!isEventOverFolder(currentFolder, ev)) { - mLauncher.closeFolder(); - return true; - } - } - return false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (handleTouchDown(ev, true)) { - return true; - } - } - clearAllResizeFrames(); - return mDragController.onInterceptTouchEvent(ev); - } - - @Override - public boolean onInterceptHoverEvent(MotionEvent ev) { - Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); - if (currentFolder == null) { - return false; - } else { - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isTouchExplorationEnabled()) { - final int action = ev.getAction(); - boolean isOverFolder; - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: - isOverFolder = isEventOverFolder(currentFolder, ev); - if (!isOverFolder) { - sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); - mHoverPointClosesFolder = true; - return true; - } else if (isOverFolder) { - mHoverPointClosesFolder = false; - } else { - return true; - } - case MotionEvent.ACTION_HOVER_MOVE: - isOverFolder = isEventOverFolder(currentFolder, ev); - if (!isOverFolder && !mHoverPointClosesFolder) { - sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); - mHoverPointClosesFolder = true; - return true; - } else if (isOverFolder) { - mHoverPointClosesFolder = false; - } else { - return true; - } - } - } - } - return false; - } - - private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_VIEW_FOCUSED); - onInitializeAccessibilityEvent(event); - event.getText().add(getContext().getString(stringId)); - accessibilityManager.sendAccessibilityEvent(event); - } - } - - @Override - public boolean onHoverEvent(MotionEvent ev) { - // If we've received this, we've already done the necessary handling - // in onInterceptHoverEvent. Return true to consume the event. - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - boolean handled = false; - int action = ev.getAction(); - - int x = (int) ev.getX(); - int y = (int) ev.getY(); - - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (handleTouchDown(ev, false)) { - return true; - } - } - } - - if (mCurrentResizeFrame != null) { - handled = true; - switch (action) { - case MotionEvent.ACTION_MOVE: - mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); - mCurrentResizeFrame.onTouchUp(); - mCurrentResizeFrame = null; - } - } - if (handled) return true; - return mDragController.onTouchEvent(ev); - } - - /** - * Determine the rect of the descendant in this DragLayer's coordinates - * - * @param descendant The descendant whose coordinates we want to find. - * @param r The rect into which to place the results. - * @return The factor by which this descendant is scaled relative to this DragLayer. - */ - public float getDescendantRectRelativeToSelf(View descendant, Rect r) { - mTmpXY[0] = 0; - mTmpXY[1] = 0; - float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); - r.set(mTmpXY[0], mTmpXY[1], - mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight()); - return scale; - } - - public void getLocationInDragLayer(View child, int[] loc) { - loc[0] = 0; - loc[1] = 0; - getDescendantCoordRelativeToSelf(child, loc); - } - - /** - * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's - * coordinates. - * - * @param descendant The descendant to which the passed coordinate is relative. - * @param coord The coordinate that we want mapped. - * @return The factor by which this descendant is scaled relative to this DragLayer. - */ - public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { - float scale = 1.0f; - float[] pt = {coord[0], coord[1]}; - descendant.getMatrix().mapPoints(pt); - scale *= descendant.getScaleX(); - pt[0] += descendant.getLeft(); - pt[1] += descendant.getTop(); - ViewParent viewParent = descendant.getParent(); - while (viewParent instanceof View && viewParent != this) { - final View view = (View)viewParent; - view.getMatrix().mapPoints(pt); - scale *= view.getScaleX(); - pt[0] += view.getLeft() - view.getScrollX(); - pt[1] += view.getTop() - view.getScrollY(); - viewParent = view.getParent(); - } - coord[0] = (int) Math.round(pt[0]); - coord[1] = (int) Math.round(pt[1]); - return scale; - } - - public void getViewRectRelativeToSelf(View v, Rect r) { - int[] loc = new int[2]; - getLocationInWindow(loc); - int x = loc[0]; - int y = loc[1]; - - v.getLocationInWindow(loc); - int vX = loc[0]; - int vY = loc[1]; - - int left = vX - x; - int top = vY - y; - r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); - } - - @Override - public boolean dispatchUnhandledMove(View focused, int direction) { - return mDragController.dispatchUnhandledMove(focused, direction); - } - - public static class LayoutParams extends FrameLayout.LayoutParams { - public int x, y; - public boolean customPosition = false; - - /** - * {@inheritDoc} - */ - public LayoutParams(int width, int height) { - super(width, height); - } - - public void setWidth(int width) { - this.width = width; - } - - public int getWidth() { - return width; - } - - public void setHeight(int height) { - this.height = height; - } - - public int getHeight() { - return height; - } - - public void setX(int x) { - this.x = x; - } - - public int getX() { - return x; - } - - public void setY(int y) { - this.y = y; - } - - public int getY() { - return y; - } - } - - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); - if (flp instanceof LayoutParams) { - final LayoutParams lp = (LayoutParams) flp; - if (lp.customPosition) { - child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); - } - } - } - } - - public void clearAllResizeFrames() { - if (mResizeFrames.size() > 0) { - for (AppWidgetResizeFrame frame: mResizeFrames) { - frame.commitResize(); - removeView(frame); - } - mResizeFrames.clear(); - } - } - - public boolean hasResizeFrames() { - return mResizeFrames.size() > 0; - } - - public boolean isWidgetBeingResized() { - return mCurrentResizeFrame != null; - } - - public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, - CellLayout cellLayout) { - AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), - widget, cellLayout, this); - - LayoutParams lp = new LayoutParams(-1, -1); - lp.customPosition = true; - - addView(resizeFrame, lp); - mResizeFrames.add(resizeFrame); - - resizeFrame.snapToWidget(false); - } - - public void animateViewIntoPosition(DragView dragView, final View child) { - animateViewIntoPosition(dragView, child, null); - } - - public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, - float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, - int duration) { - Rect r = new Rect(); - getViewRectRelativeToSelf(dragView, r); - final int fromX = r.left; - final int fromY = r.top; - - animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY, - onFinishRunnable, animationEndStyle, duration, null); - } - - public void animateViewIntoPosition(DragView dragView, final View child, - final Runnable onFinishAnimationRunnable) { - animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null); - } - - public void animateViewIntoPosition(DragView dragView, final View child, int duration, - final Runnable onFinishAnimationRunnable, View anchorView) { - ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); - CellLayout parent = (CellLayout) (CellLayout) parentChildren.getParent(); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - parentChildren.measureChild(child); - - Rect r = new Rect(); - getViewRectRelativeToSelf(dragView, r); - - int coord[] = new int[2]; - coord[0] = lp.x; - coord[1] = lp.y; - - // Since the child hasn't necessarily been laid out, we force the lp to be updated with - // the correct coordinates (above) and use these to determine the final location - float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); - int toX = coord[0]; - int toY = coord[1]; - if (child instanceof TextView) { - TextView tv = (TextView) child; - - // The child may be scaled (always about the center of the view) so to account for it, - // we have to offset the position by the scaled size. Once we do that, we can center - // the drag view about the scaled child view. - toY += Math.round(scale * tv.getPaddingTop()); - toY -= dragView.getMeasuredHeight() * (1 - scale) / 2; - toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; - } else if (child instanceof FolderIcon) { - // Account for holographic blur padding on the drag view - toY -= Workspace.DRAG_BITMAP_PADDING / 2; - // Center in the x coordinate about the target's drawable - toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; - } else { - toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; - toX -= (Math.round(scale * (dragView.getMeasuredWidth() - - child.getMeasuredWidth()))) / 2; - } - - final int fromX = r.left; - final int fromY = r.top; - child.setVisibility(INVISIBLE); - Runnable onCompleteRunnable = new Runnable() { - public void run() { - child.setVisibility(VISIBLE); - if (onFinishAnimationRunnable != null) { - onFinishAnimationRunnable.run(); - } - } - }; - animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale, - onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); - } - - public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, - final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, - float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, - int animationEndStyle, int duration, View anchorView) { - Rect from = new Rect(fromX, fromY, fromX + - view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); - Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); - animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, - null, null, onCompleteRunnable, animationEndStyle, anchorView); - } - - /** - * This method animates a view at the end of a drag and drop animation. - * - * @param view The view to be animated. This view is drawn directly into DragLayer, and so - * doesn't need to be a child of DragLayer. - * @param from The initial location of the view. Only the left and top parameters are used. - * @param to The final location of the view. Only the left and top parameters are used. This - * location doesn't account for scaling, and so should be centered about the desired - * final location (including scaling). - * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. - * @param finalScale The final scale of the view. The view is scaled about its center. - * @param duration The duration of the animation. - * @param motionInterpolator The interpolator to use for the location of the view. - * @param alphaInterpolator The interpolator to use for the alpha of the view. - * @param onCompleteRunnable Optional runnable to run on animation completion. - * @param fadeOut Whether or not to fade out the view once the animation completes. If true, - * the runnable will execute after the view is faded out. - * @param anchorView If not null, this represents the view which the animated view stays - * anchored to in case scrolling is currently taking place. Note: currently this is - * only used for the X dimension for the case of the workspace. - */ - public void animateView(final DragView view, final Rect from, final Rect to, - final float finalAlpha, final float initScaleX, final float initScaleY, - final float finalScaleX, final float finalScaleY, int duration, - final Interpolator motionInterpolator, final Interpolator alphaInterpolator, - final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { - - // Calculate the duration of the animation based on the object's distance - final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) + - Math.pow(to.top - from.top, 2)); - final Resources res = getResources(); - final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); - - // If duration < 0, this is a cue to compute the duration based on the distance - if (duration < 0) { - duration = res.getInteger(R.integer.config_dropAnimMaxDuration); - if (dist < maxDist) { - duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); - } - duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); - } - - // Fall back to cubic ease out interpolator for the animation if none is specified - TimeInterpolator interpolator = null; - if (alphaInterpolator == null || motionInterpolator == null) { - interpolator = mCubicEaseOutInterpolator; - } - - // Animate the view - final float initAlpha = view.getAlpha(); - final float dropViewScale = view.getScaleX(); - AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - final int width = view.getMeasuredWidth(); - final int height = view.getMeasuredHeight(); - - float alphaPercent = alphaInterpolator == null ? percent : - alphaInterpolator.getInterpolation(percent); - float motionPercent = motionInterpolator == null ? percent : - motionInterpolator.getInterpolation(percent); - - float initialScaleX = initScaleX * dropViewScale; - float initialScaleY = initScaleY * dropViewScale; - float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); - float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); - float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); - - float fromLeft = from.left + (initialScaleX - 1f) * width / 2; - float fromTop = from.top + (initialScaleY - 1f) * height / 2; - - int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); - int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); - - int xPos = x - mDropView.getScrollX() + (mAnchorView != null - ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0); - int yPos = y - mDropView.getScrollY(); - - mDropView.setTranslationX(xPos); - mDropView.setTranslationY(yPos); - mDropView.setScaleX(scaleX); - mDropView.setScaleY(scaleY); - mDropView.setAlpha(alpha); - } - }; - animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, - anchorView); - } - - public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, - TimeInterpolator interpolator, final Runnable onCompleteRunnable, - final int animationEndStyle, View anchorView) { - // Clean up the previous animations - if (mDropAnim != null) mDropAnim.cancel(); - if (mFadeOutAnim != null) mFadeOutAnim.cancel(); - - // Show the drop view if it was previously hidden - mDropView = view; - mDropView.cancelAnimation(); - mDropView.resetLayoutParams(); - - // Set the anchor view if the page is scrolling - if (anchorView != null) { - mAnchorViewInitialScrollX = anchorView.getScrollX(); - } - mAnchorView = anchorView; - - // Create and start the animation - mDropAnim = new ValueAnimator(); - mDropAnim.setInterpolator(interpolator); - mDropAnim.setDuration(duration); - mDropAnim.setFloatValues(0f, 1f); - mDropAnim.addUpdateListener(updateCb); - mDropAnim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - switch (animationEndStyle) { - case ANIMATION_END_DISAPPEAR: - clearAnimatedView(); - break; - case ANIMATION_END_FADE_OUT: - fadeOutDragView(); - break; - case ANIMATION_END_REMAIN_VISIBLE: - break; - } - } - }); - mDropAnim.start(); - } - - public void clearAnimatedView() { - if (mDropAnim != null) { - mDropAnim.cancel(); - } - if (mDropView != null) { - mDragController.onDeferredEndDrag(mDropView); - } - mDropView = null; - invalidate(); - } - - public View getAnimatedView() { - return mDropView; - } - - private void fadeOutDragView() { - mFadeOutAnim = new ValueAnimator(); - mFadeOutAnim.setDuration(150); - mFadeOutAnim.setFloatValues(0f, 1f); - mFadeOutAnim.removeAllUpdateListeners(); - mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - - float alpha = 1 - percent; - mDropView.setAlpha(alpha); - } - }); - mFadeOutAnim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - if (mDropView != null) { - mDragController.onDeferredEndDrag(mDropView); - } - mDropView = null; - invalidate(); - } - }); - mFadeOutAnim.start(); - } - - @Override - public void onChildViewAdded(View parent, View child) { - updateChildIndices(); - } - - @Override - public void onChildViewRemoved(View parent, View child) { - updateChildIndices(); - } - - private void updateChildIndices() { - if (mLauncher != null) { - mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace()); - mQsbIndex = indexOfChild(mLauncher.getSearchBar()); - } - } - - @Override - protected int getChildDrawingOrder(int childCount, int i) { - // We don't want to prioritize the workspace drawing on top of the other children in - // landscape for the overscroll event. - if (LauncherApplication.isScreenLandscape(getContext())) { - return super.getChildDrawingOrder(childCount, i); - } - - if (mWorkspaceIndex == -1 || mQsbIndex == -1 || - mLauncher.getWorkspace().isDrawingBackgroundGradient()) { - return i; - } - - // This ensures that the workspace is drawn above the hotseat and qsb, - // except when the workspace is drawing a background gradient, in which - // case we want the workspace to stay behind these elements. - if (i == mQsbIndex) { - return mWorkspaceIndex; - } else if (i == mWorkspaceIndex) { - return mQsbIndex; - } else { - return i; - } - } - - private boolean mInScrollArea; - private Drawable mLeftHoverDrawable; - private Drawable mRightHoverDrawable; - - void onEnterScrollArea(int direction) { - mInScrollArea = true; - invalidate(); - } - - void onExitScrollArea() { - mInScrollArea = false; - invalidate(); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - if (mInScrollArea && !LauncherApplication.isScreenLarge()) { - Workspace workspace = mLauncher.getWorkspace(); - int width = workspace.getWidth(); - Rect childRect = new Rect(); - getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect); - - int page = workspace.getNextPage(); - CellLayout leftPage = (CellLayout) workspace.getChildAt(page - 1); - CellLayout rightPage = (CellLayout) workspace.getChildAt(page + 1); - - if (leftPage != null && leftPage.getIsDragOverlapping()) { - mLeftHoverDrawable.setBounds(0, childRect.top, - mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom); - mLeftHoverDrawable.draw(canvas); - } else if (rightPage != null && rightPage.getIsDragOverlapping()) { - mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(), - childRect.top, width, childRect.bottom); - mRightHoverDrawable.draw(canvas); - } - } - } -} diff --git a/src/com/android/launcher2/DragScroller.java b/src/com/android/launcher2/DragScroller.java deleted file mode 100644 index a3ee6c237..000000000 --- a/src/com/android/launcher2/DragScroller.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -/** - * Handles scrolling while dragging - * - */ -public interface DragScroller { - void scrollLeft(); - void scrollRight(); - - /** - * The touch point has entered the scroll area; a scroll is imminent. - * This event will only occur while a drag is active. - * - * @param direction The scroll direction - */ - boolean onEnterScrollArea(int x, int y, int direction); - - /** - * The touch point has left the scroll area. - * NOTE: This may not be called, if a drop occurs inside the scroll area. - */ - boolean onExitScrollArea(); -} diff --git a/src/com/android/launcher2/DragSource.java b/src/com/android/launcher2/DragSource.java deleted file mode 100644 index 54404770a..000000000 --- a/src/com/android/launcher2/DragSource.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.view.View; - -import com.android.launcher2.DropTarget.DragObject; - -/** - * Interface defining an object that can originate a drag. - * - */ -public interface DragSource { - /** - * @return whether items dragged from this source supports - */ - boolean supportsFlingToDelete(); - - /** - * A callback specifically made back to the source after an item from this source has been flung - * to be deleted on a DropTarget. In such a situation, this method will be called after - * onDropCompleted, and more importantly, after the fling animation has completed. - */ - void onFlingToDeleteCompleted(); - - /** - * A callback made back to the source after an item from this source has been dropped on a - * DropTarget. - */ - void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success); -} diff --git a/src/com/android/launcher2/DragView.java b/src/com/android/launcher2/DragView.java deleted file mode 100644 index b6645e102..000000000 --- a/src/com/android/launcher2/DragView.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.android.launcher2; - -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.view.View; -import android.view.animation.DecelerateInterpolator; - -import com.android.launcher.R; - -public class DragView extends View { - private static float sDragAlpha = 1f; - - private Bitmap mBitmap; - private Bitmap mCrossFadeBitmap; - private Paint mPaint; - private int mRegistrationX; - private int mRegistrationY; - - private Point mDragVisualizeOffset = null; - private Rect mDragRegion = null; - private DragLayer mDragLayer = null; - private boolean mHasDrawn = false; - private float mCrossFadeProgress = 0f; - - ValueAnimator mAnim; - private float mOffsetX = 0.0f; - private float mOffsetY = 0.0f; - private float mInitialScale = 1f; - - /** - * Construct the drag view. - *

- * The registration point is the point inside our view that the touch events should - * be centered upon. - * - * @param launcher The Launcher instance - * @param bitmap The view that we're dragging around. We scale it up when we draw it. - * @param registrationX The x coordinate of the registration point. - * @param registrationY The y coordinate of the registration point. - */ - public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, - int left, int top, int width, int height, final float initialScale) { - super(launcher); - mDragLayer = launcher.getDragLayer(); - mInitialScale = initialScale; - - final Resources res = getResources(); - final float offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX); - final float offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY); - final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale); - final float scale = (width + scaleDps) / width; - - // Animate the view into the correct position - mAnim = ValueAnimator.ofFloat(0.0f, 1.0f); - mAnim.setDuration(150); - mAnim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - final float value = (Float) animation.getAnimatedValue(); - - final int deltaX = (int) ((value * offsetX) - mOffsetX); - final int deltaY = (int) ((value * offsetY) - mOffsetY); - - mOffsetX += deltaX; - mOffsetY += deltaY; - setScaleX(initialScale + (value * (scale - initialScale))); - setScaleY(initialScale + (value * (scale - initialScale))); - if (sDragAlpha != 1f) { - setAlpha(sDragAlpha * value + (1f - value)); - } - - if (getParent() == null) { - animation.cancel(); - } else { - setTranslationX(getTranslationX() + deltaX); - setTranslationY(getTranslationY() + deltaY); - } - } - }); - - mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height); - setDragRegion(new Rect(0, 0, width, height)); - - // The point in our scaled bitmap that the touch events are located - mRegistrationX = registrationX; - mRegistrationY = registrationY; - - // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass - int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - measure(ms, ms); - mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); - } - - public float getOffsetY() { - return mOffsetY; - } - - public int getDragRegionLeft() { - return mDragRegion.left; - } - - public int getDragRegionTop() { - return mDragRegion.top; - } - - public int getDragRegionWidth() { - return mDragRegion.width(); - } - - public int getDragRegionHeight() { - return mDragRegion.height(); - } - - public void setDragVisualizeOffset(Point p) { - mDragVisualizeOffset = p; - } - - public Point getDragVisualizeOffset() { - return mDragVisualizeOffset; - } - - public void setDragRegion(Rect r) { - mDragRegion = r; - } - - public Rect getDragRegion() { - return mDragRegion; - } - - public float getInitialScale() { - return mInitialScale; - } - - public void updateInitialScaleToCurrentScale() { - mInitialScale = getScaleX(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); - } - - @Override - protected void onDraw(Canvas canvas) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(0x66ffffff); - canvas.drawRect(0, 0, getWidth(), getHeight(), p); - } - - mHasDrawn = true; - boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null; - if (crossFade) { - int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255; - mPaint.setAlpha(alpha); - } - canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); - if (crossFade) { - mPaint.setAlpha((int) (255 * mCrossFadeProgress)); - canvas.save(); - float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth(); - float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight(); - canvas.scale(sX, sY); - canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint); - canvas.restore(); - } - } - - public void setCrossFadeBitmap(Bitmap crossFadeBitmap) { - mCrossFadeBitmap = crossFadeBitmap; - } - - public void crossFade(int duration) { - ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - va.setDuration(duration); - va.setInterpolator(new DecelerateInterpolator(1.5f)); - va.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mCrossFadeProgress = animation.getAnimatedFraction(); - } - }); - va.start(); - } - - public void setColor(int color) { - if (mPaint == null) { - mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); - } - if (color != 0) { - mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); - } else { - mPaint.setColorFilter(null); - } - invalidate(); - } - - public boolean hasDrawn() { - return mHasDrawn; - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - mPaint.setAlpha((int) (255 * alpha)); - invalidate(); - } - - /** - * Create a window containing this view and show it. - * - * @param windowToken obtained from v.getWindowToken() from one of your views - * @param touchX the x coordinate the user touched in DragLayer coordinates - * @param touchY the y coordinate the user touched in DragLayer coordinates - */ - public void show(int touchX, int touchY) { - mDragLayer.addView(this); - - // Enable hw-layers on this view - setLayerType(View.LAYER_TYPE_HARDWARE, null); - - // Start the pick-up animation - DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0); - lp.width = mBitmap.getWidth(); - lp.height = mBitmap.getHeight(); - lp.customPosition = true; - setLayoutParams(lp); - setTranslationX(touchX - mRegistrationX); - setTranslationY(touchY - mRegistrationY); - mAnim.start(); - } - - public void cancelAnimation() { - if (mAnim != null && mAnim.isRunning()) { - mAnim.cancel(); - } - } - - public void resetLayoutParams() { - mOffsetX = mOffsetY = 0; - requestLayout(); - } - - /** - * Move the window containing this view. - * - * @param touchX the x coordinate the user touched in DragLayer coordinates - * @param touchY the y coordinate the user touched in DragLayer coordinates - */ - void move(int touchX, int touchY) { - setTranslationX(touchX - mRegistrationX + (int) mOffsetX); - setTranslationY(touchY - mRegistrationY + (int) mOffsetY); - } - - void remove() { - if (getParent() != null) { - // Disable hw-layers on this view - setLayerType(View.LAYER_TYPE_NONE, null); - - mDragLayer.removeView(DragView.this); - } - } -} - diff --git a/src/com/android/launcher2/DrawableStateProxyView.java b/src/com/android/launcher2/DrawableStateProxyView.java deleted file mode 100644 index 5d2f6e0a2..000000000 --- a/src/com/android/launcher2/DrawableStateProxyView.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2012 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.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.LinearLayout; - -import com.android.launcher.R; - -public class DrawableStateProxyView extends LinearLayout { - - private View mView; - private int mViewId; - - public DrawableStateProxyView(Context context) { - this(context, null); - } - - public DrawableStateProxyView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - - public DrawableStateProxyView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableStateProxyView, - defStyle, 0); - mViewId = a.getResourceId(R.styleable.DrawableStateProxyView_sourceViewId, -1); - a.recycle(); - - setFocusable(false); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - if (mView == null) { - View parent = (View) getParent(); - mView = parent.findViewById(mViewId); - } - mView.setPressed(isPressed()); - mView.setHovered(isHovered()); - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - return false; - } -} diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java deleted file mode 100644 index d627a4c2e..000000000 --- a/src/com/android/launcher2/DropTarget.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.graphics.PointF; -import android.graphics.Rect; -import android.util.Log; - -/** - * Interface defining an object that can receive a drag. - * - */ -public interface DropTarget { - - public static final String TAG = "DropTarget"; - - class DragObject { - public int x = -1; - public int y = -1; - - /** X offset from the upper-left corner of the cell to where we touched. */ - public int xOffset = -1; - - /** Y offset from the upper-left corner of the cell to where we touched. */ - public int yOffset = -1; - - /** This indicates whether a drag is in final stages, either drop or cancel. It - * differentiates onDragExit, since this is called when the drag is ending, above - * the current drag target, or when the drag moves off the current drag object. - */ - public boolean dragComplete = false; - - /** The view that moves around while you drag. */ - public DragView dragView = null; - - /** The data associated with the object being dragged */ - public Object dragInfo = null; - - /** Where the drag originated */ - public DragSource dragSource = null; - - /** Post drag animation runnable */ - public Runnable postAnimationRunnable = null; - - /** Indicates that the drag operation was cancelled */ - public boolean cancelled = false; - - /** Defers removing the DragView from the DragLayer until after the drop animation. */ - public boolean deferDragViewCleanupPostAnimation = true; - - public DragObject() { - } - } - - public static class DragEnforcer implements DragController.DragListener { - int dragParity = 0; - - public DragEnforcer(Context context) { - Launcher launcher = (Launcher) context; - launcher.getDragController().addDragListener(this); - } - - void onDragEnter() { - dragParity++; - if (dragParity != 1) { - Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); - } - } - - void onDragExit() { - dragParity--; - if (dragParity != 0) { - Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); - } - } - - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - if (dragParity != 0) { - Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); - } - } - - @Override - public void onDragEnd() { - if (dragParity != 0) { - Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); - } - } - } - - /** - * 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 - * - * @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 dragView The DragView that's being dragged around on screen. - * @param dragInfo Data associated with the object being dragged - * - */ - void onDrop(DragObject dragObject); - - void onDragEnter(DragObject dragObject); - - void onDragOver(DragObject dragObject); - - void onDragExit(DragObject dragObject); - - /** - * Handle an object being dropped as a result of flinging to delete and will be called in place - * of onDrop(). (This is only called on objects that are set as the DragController's - * fling-to-delete target. - */ - void onFlingToDelete(DragObject dragObject, int x, int y, PointF vec); - - /** - * 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 dragView The DragView that's being dragged around on screen. - * @param dragInfo Data associated with the object being dragged - * - * @return The DropTarget to delegate to, or null to not delegate to another object. - */ - DropTarget getDropTargetDelegate(DragObject dragObject); - - /** - * 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 - * @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 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. - */ - boolean acceptDrop(DragObject dragObject); - - // These methods are implemented in Views - void getHitRect(Rect outRect); - void getLocationInDragLayer(int[] loc); - int getLeft(); - int getTop(); -} diff --git a/src/com/android/launcher2/FastBitmapDrawable.java b/src/com/android/launcher2/FastBitmapDrawable.java deleted file mode 100644 index d317d3302..000000000 --- a/src/com/android/launcher2/FastBitmapDrawable.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -class FastBitmapDrawable extends Drawable { - private Bitmap mBitmap; - private int mAlpha; - private int mWidth; - private int mHeight; - private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); - - FastBitmapDrawable(Bitmap b) { - mAlpha = 255; - mBitmap = b; - if (b != null) { - mWidth = mBitmap.getWidth(); - mHeight = mBitmap.getHeight(); - } else { - mWidth = mHeight = 0; - } - } - - @Override - public void draw(Canvas canvas) { - final Rect r = getBounds(); - canvas.drawBitmap(mBitmap, r.left, r.top, mPaint); - } - - @Override - public void setColorFilter(ColorFilter cf) { - mPaint.setColorFilter(cf); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - mAlpha = alpha; - mPaint.setAlpha(alpha); - } - - public void setFilterBitmap(boolean filterBitmap) { - mPaint.setFilterBitmap(filterBitmap); - } - - public int getAlpha() { - return mAlpha; - } - - @Override - public int getIntrinsicWidth() { - return mWidth; - } - - @Override - public int getIntrinsicHeight() { - return mHeight; - } - - @Override - public int getMinimumWidth() { - return mWidth; - } - - @Override - public int getMinimumHeight() { - return mHeight; - } - - public void setBitmap(Bitmap b) { - mBitmap = b; - if (b != null) { - mWidth = mBitmap.getWidth(); - mHeight = mBitmap.getHeight(); - } else { - mWidth = mHeight = 0; - } - } - - public Bitmap getBitmap() { - return mBitmap; - } -} diff --git a/src/com/android/launcher2/FocusHelper.java b/src/com/android/launcher2/FocusHelper.java deleted file mode 100644 index e9f986d76..000000000 --- a/src/com/android/launcher2/FocusHelper.java +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.res.Configuration; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.TabHost; -import android.widget.TabWidget; - -import com.android.launcher.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * A keyboard listener we set on all the workspace icons. - */ -class IconKeyEventListener implements View.OnKeyListener { - public boolean onKey(View v, int keyCode, KeyEvent event) { - return FocusHelper.handleIconKeyEvent(v, keyCode, event); - } -} - -/** - * A keyboard listener we set on all the workspace icons. - */ -class FolderKeyEventListener implements View.OnKeyListener { - public boolean onKey(View v, int keyCode, KeyEvent event) { - return FocusHelper.handleFolderKeyEvent(v, keyCode, event); - } -} - -/** - * A keyboard listener we set on all the hotseat buttons. - */ -class HotseatIconKeyEventListener implements View.OnKeyListener { - public boolean onKey(View v, int keyCode, KeyEvent event) { - final Configuration configuration = v.getResources().getConfiguration(); - return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); - } -} - -/** - * A keyboard listener we set on the last tab button in AppsCustomize to jump to then - * market icon and vice versa. - */ -class AppsCustomizeTabKeyEventListener implements View.OnKeyListener { - public boolean onKey(View v, int keyCode, KeyEvent event) { - return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event); - } -} - -public class FocusHelper { - /** - * Private helper to get the parent TabHost in the view hiearchy. - */ - private static TabHost findTabHostParent(View v) { - ViewParent p = v.getParent(); - while (p != null && !(p instanceof TabHost)) { - p = p.getParent(); - } - return (TabHost) p; - } - - /** - * Handles key events in a AppsCustomize tab between the last tab view and the shop button. - */ - static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) { - final TabHost tabHost = findTabHostParent(v); - final ViewGroup contents = tabHost.getTabContentView(); - final View shop = tabHost.findViewById(R.id.market_button); - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the shop button if we aren't on it - if (v != shop) { - shop.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (handleKeyEvent) { - // Select the content view (down is handled by the tab key handler otherwise) - if (v == shop) { - contents.requestFocus(); - wasHandled = true; - } - } - break; - default: break; - } - return wasHandled; - } - - /** - * Returns the Viewgroup containing page contents for the page at the index specified. - */ - private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) { - ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index); - if (page instanceof PagedViewCellLayout) { - // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren - page = (ViewGroup) page.getChildAt(0); - } - return page; - } - - /** - * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets. - */ - static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode, - KeyEvent e) { - - final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent(); - final PagedView container = (PagedView) parent.getParent(); - final TabHost tabHost = findTabHostParent(container); - final TabWidget tabs = tabHost.getTabWidget(); - final int widgetIndex = parent.indexOfChild(w); - final int widgetCount = parent.getChildCount(); - final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent)); - final int pageCount = container.getChildCount(); - final int cellCountX = parent.getCellCountX(); - final int cellCountY = parent.getCellCountY(); - final int x = widgetIndex % cellCountX; - final int y = widgetIndex / cellCountX; - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - ViewGroup newParent = null; - // Now that we load items in the bg asynchronously, we can't just focus - // child siblings willy-nilly - View child = null; - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (handleKeyEvent) { - // Select the previous widget or the last widget on the previous page - if (widgetIndex > 0) { - parent.getChildAt(widgetIndex - 1).requestFocus(); - } else { - if (pageIndex > 0) { - newParent = getAppsCustomizePage(container, pageIndex - 1); - if (newParent != null) { - child = newParent.getChildAt(newParent.getChildCount() - 1); - if (child != null) child.requestFocus(); - } - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the next widget or the first widget on the next page - if (widgetIndex < (widgetCount - 1)) { - parent.getChildAt(widgetIndex + 1).requestFocus(); - } else { - if (pageIndex < (pageCount - 1)) { - newParent = getAppsCustomizePage(container, pageIndex + 1); - if (newParent != null) { - child = newParent.getChildAt(0); - if (child != null) child.requestFocus(); - } - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (handleKeyEvent) { - // Select the closest icon in the previous row, otherwise select the tab bar - if (y > 0) { - int newWidgetIndex = ((y - 1) * cellCountX) + x; - child = parent.getChildAt(newWidgetIndex); - if (child != null) child.requestFocus(); - } else { - tabs.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (handleKeyEvent) { - // Select the closest icon in the previous row, otherwise do nothing - if (y < (cellCountY - 1)) { - int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x); - child = parent.getChildAt(newWidgetIndex); - if (child != null) child.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - if (handleKeyEvent) { - // Simulate a click on the widget - View.OnClickListener clickListener = (View.OnClickListener) container; - clickListener.onClick(w); - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_PAGE_UP: - if (handleKeyEvent) { - // Select the first item on the previous page, or the first item on this page - // if there is no previous page - if (pageIndex > 0) { - newParent = getAppsCustomizePage(container, pageIndex - 1); - if (newParent != null) { - child = newParent.getChildAt(0); - } - } else { - child = parent.getChildAt(0); - } - if (child != null) child.requestFocus(); - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_PAGE_DOWN: - if (handleKeyEvent) { - // Select the first item on the next page, or the last item on this page - // if there is no next page - if (pageIndex < (pageCount - 1)) { - newParent = getAppsCustomizePage(container, pageIndex + 1); - if (newParent != null) { - child = newParent.getChildAt(0); - } - } else { - child = parent.getChildAt(widgetCount - 1); - } - if (child != null) child.requestFocus(); - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_HOME: - if (handleKeyEvent) { - // Select the first item on this page - child = parent.getChildAt(0); - if (child != null) child.requestFocus(); - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_END: - if (handleKeyEvent) { - // Select the last item on this page - parent.getChildAt(widgetCount - 1).requestFocus(); - } - wasHandled = true; - break; - default: break; - } - return wasHandled; - } - - /** - * Handles key events in a PageViewCellLayout containing PagedViewIcons. - */ - static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) { - ViewGroup parentLayout; - ViewGroup itemContainer; - int countX; - int countY; - if (v.getParent() instanceof PagedViewCellLayoutChildren) { - itemContainer = (ViewGroup) v.getParent(); - parentLayout = (ViewGroup) itemContainer.getParent(); - countX = ((PagedViewCellLayout) parentLayout).getCellCountX(); - countY = ((PagedViewCellLayout) parentLayout).getCellCountY(); - } else { - itemContainer = parentLayout = (ViewGroup) v.getParent(); - countX = ((PagedViewGridLayout) parentLayout).getCellCountX(); - countY = ((PagedViewGridLayout) parentLayout).getCellCountY(); - } - - // Note we have an extra parent because of the - // PagedViewCellLayout/PagedViewCellLayoutChildren relationship - final PagedView container = (PagedView) parentLayout.getParent(); - final TabHost tabHost = findTabHostParent(container); - final TabWidget tabs = tabHost.getTabWidget(); - final int iconIndex = itemContainer.indexOfChild(v); - final int itemCount = itemContainer.getChildCount(); - final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout)); - final int pageCount = container.getChildCount(); - - final int x = iconIndex % countX; - final int y = iconIndex / countX; - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - ViewGroup newParent = null; - // Side pages do not always load synchronously, so check before focusing child siblings - // willy-nilly - View child = null; - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (handleKeyEvent) { - // Select the previous icon or the last icon on the previous page - if (iconIndex > 0) { - itemContainer.getChildAt(iconIndex - 1).requestFocus(); - } else { - if (pageIndex > 0) { - newParent = getAppsCustomizePage(container, pageIndex - 1); - if (newParent != null) { - container.snapToPage(pageIndex - 1); - child = newParent.getChildAt(newParent.getChildCount() - 1); - if (child != null) child.requestFocus(); - } - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the next icon or the first icon on the next page - if (iconIndex < (itemCount - 1)) { - itemContainer.getChildAt(iconIndex + 1).requestFocus(); - } else { - if (pageIndex < (pageCount - 1)) { - newParent = getAppsCustomizePage(container, pageIndex + 1); - if (newParent != null) { - container.snapToPage(pageIndex + 1); - child = newParent.getChildAt(0); - if (child != null) child.requestFocus(); - } - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (handleKeyEvent) { - // Select the closest icon in the previous row, otherwise select the tab bar - if (y > 0) { - int newiconIndex = ((y - 1) * countX) + x; - itemContainer.getChildAt(newiconIndex).requestFocus(); - } else { - tabs.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (handleKeyEvent) { - // Select the closest icon in the previous row, otherwise do nothing - if (y < (countY - 1)) { - int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x); - itemContainer.getChildAt(newiconIndex).requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - if (handleKeyEvent) { - // Simulate a click on the icon - View.OnClickListener clickListener = (View.OnClickListener) container; - clickListener.onClick(v); - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_PAGE_UP: - if (handleKeyEvent) { - // Select the first icon on the previous page, or the first icon on this page - // if there is no previous page - if (pageIndex > 0) { - newParent = getAppsCustomizePage(container, pageIndex - 1); - if (newParent != null) { - container.snapToPage(pageIndex - 1); - child = newParent.getChildAt(0); - if (child != null) child.requestFocus(); - } - } else { - itemContainer.getChildAt(0).requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_PAGE_DOWN: - if (handleKeyEvent) { - // Select the first icon on the next page, or the last icon on this page - // if there is no next page - if (pageIndex < (pageCount - 1)) { - newParent = getAppsCustomizePage(container, pageIndex + 1); - if (newParent != null) { - container.snapToPage(pageIndex + 1); - child = newParent.getChildAt(0); - if (child != null) child.requestFocus(); - } - } else { - itemContainer.getChildAt(itemCount - 1).requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_HOME: - if (handleKeyEvent) { - // Select the first icon on this page - itemContainer.getChildAt(0).requestFocus(); - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_END: - if (handleKeyEvent) { - // Select the last icon on this page - itemContainer.getChildAt(itemCount - 1).requestFocus(); - } - wasHandled = true; - break; - default: break; - } - return wasHandled; - } - - /** - * Handles key events in the tab widget. - */ - static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) { - if (!LauncherApplication.isScreenLarge()) return false; - - final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent(); - final TabHost tabHost = findTabHostParent(parent); - final ViewGroup contents = tabHost.getTabContentView(); - final int tabCount = parent.getTabCount(); - final int tabIndex = parent.getChildTabIndex(v); - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (handleKeyEvent) { - // Select the previous tab - if (tabIndex > 0) { - parent.getChildTabViewAt(tabIndex - 1).requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the next tab, or if the last tab has a focus right id, select that - if (tabIndex < (tabCount - 1)) { - parent.getChildTabViewAt(tabIndex + 1).requestFocus(); - } else { - if (v.getNextFocusRightId() != View.NO_ID) { - tabHost.findViewById(v.getNextFocusRightId()).requestFocus(); - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_UP: - // Do nothing - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (handleKeyEvent) { - // Select the content view - contents.requestFocus(); - } - wasHandled = true; - break; - default: break; - } - return wasHandled; - } - - /** - * Handles key events in the workspace hotseat (bottom of the screen). - */ - static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) { - final ViewGroup parent = (ViewGroup) v.getParent(); - final ViewGroup launcher = (ViewGroup) parent.getParent(); - final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace); - final int buttonIndex = parent.indexOfChild(v); - final int buttonCount = parent.getChildCount(); - final int pageIndex = workspace.getCurrentPage(); - - // NOTE: currently we don't special case for the phone UI in different - // orientations, even though the hotseat is on the side in landscape mode. This - // is to ensure that accessibility consistency is maintained across rotations. - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (handleKeyEvent) { - // Select the previous button, otherwise snap to the previous page - if (buttonIndex > 0) { - parent.getChildAt(buttonIndex - 1).requestFocus(); - } else { - workspace.snapToPage(pageIndex - 1); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the next button, otherwise snap to the next page - if (buttonIndex < (buttonCount - 1)) { - parent.getChildAt(buttonIndex + 1).requestFocus(); - } else { - workspace.snapToPage(pageIndex + 1); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (handleKeyEvent) { - // Select the first bubble text view in the current page of the workspace - final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex); - final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets(); - final View newIcon = getIconInDirection(layout, children, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - workspace.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - // Do nothing - wasHandled = true; - break; - default: break; - } - return wasHandled; - } - - /** - * Private helper method to get the CellLayoutChildren given a CellLayout index. - */ - private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex( - ViewGroup container, int i) { - ViewGroup parent = (ViewGroup) container.getChildAt(i); - return (ShortcutAndWidgetContainer) parent.getChildAt(0); - } - - /** - * Private helper method to sort all the CellLayout children in order of their (x,y) spatially - * from top left to bottom right. - */ - private static ArrayList getCellLayoutChildrenSortedSpatially(CellLayout layout, - ViewGroup parent) { - // First we order each the CellLayout children by their x,y coordinates - final int cellCountX = layout.getCountX(); - final int count = parent.getChildCount(); - ArrayList views = new ArrayList(); - for (int j = 0; j < count; ++j) { - views.add(parent.getChildAt(j)); - } - Collections.sort(views, new Comparator() { - @Override - public int compare(View lhs, View rhs) { - CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams(); - CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams(); - int lvIndex = (llp.cellY * cellCountX) + llp.cellX; - int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX; - return lvIndex - rvIndex; - } - }); - return views; - } - /** - * Private helper method to find the index of the next BubbleTextView or FolderIcon in the - * direction delta. - * - * @param delta either -1 or 1 depending on the direction we want to search - */ - private static View findIndexOfIcon(ArrayList views, int i, int delta) { - // Then we find the next BubbleTextView offset by delta from i - final int count = views.size(); - int newI = i + delta; - while (0 <= newI && newI < count) { - View newV = views.get(newI); - if (newV instanceof BubbleTextView || newV instanceof FolderIcon) { - return newV; - } - newI += delta; - } - return null; - } - private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i, - int delta) { - final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); - return findIndexOfIcon(views, i, delta); - } - private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v, - int delta) { - final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); - return findIndexOfIcon(views, views.indexOf(v), delta); - } - /** - * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction - * delta on the next line. - * - * @param delta either -1 or 1 depending on the line and direction we want to search - */ - private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v, - int lineDelta) { - final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); - final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); - final int cellCountY = layout.getCountY(); - final int row = lp.cellY; - final int newRow = row + lineDelta; - if (0 <= newRow && newRow < cellCountY) { - float closestDistance = Float.MAX_VALUE; - int closestIndex = -1; - int index = views.indexOf(v); - int endIndex = (lineDelta < 0) ? -1 : views.size(); - while (index != endIndex) { - View newV = views.get(index); - CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams(); - boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row); - if (satisfiesRow && - (newV instanceof BubbleTextView || newV instanceof FolderIcon)) { - float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) + - Math.pow(tmpLp.cellY - lp.cellY, 2)); - if (tmpDistance < closestDistance) { - closestIndex = index; - closestDistance = tmpDistance; - } - } - if (index <= endIndex) { - ++index; - } else { - --index; - } - } - if (closestIndex > -1) { - return views.get(closestIndex); - } - } - return null; - } - - /** - * Handles key events in a Workspace containing. - */ - static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { - ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); - final CellLayout layout = (CellLayout) parent.getParent(); - final Workspace workspace = (Workspace) layout.getParent(); - final ViewGroup launcher = (ViewGroup) workspace.getParent(); - final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar); - final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat); - int pageIndex = workspace.indexOfChild(layout); - int pageCount = workspace.getChildCount(); - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (handleKeyEvent) { - // Select the previous icon or the last icon on the previous page if possible - View newIcon = getIconInDirection(layout, parent, v, -1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - if (pageIndex > 0) { - parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); - newIcon = getIconInDirection(layout, parent, - parent.getChildCount(), -1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - // Snap to the previous page - workspace.snapToPage(pageIndex - 1); - } - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the next icon or the first icon on the next page if possible - View newIcon = getIconInDirection(layout, parent, v, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - if (pageIndex < (pageCount - 1)) { - parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); - newIcon = getIconInDirection(layout, parent, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - // Snap to the next page - workspace.snapToPage(pageIndex + 1); - } - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (handleKeyEvent) { - // Select the closest icon in the previous line, otherwise select the tab bar - View newIcon = getClosestIconOnLine(layout, parent, v, -1); - if (newIcon != null) { - newIcon.requestFocus(); - wasHandled = true; - } else { - tabs.requestFocus(); - } - } - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (handleKeyEvent) { - // Select the closest icon in the next line, otherwise select the button bar - View newIcon = getClosestIconOnLine(layout, parent, v, 1); - if (newIcon != null) { - newIcon.requestFocus(); - wasHandled = true; - } else if (hotseat != null) { - hotseat.requestFocus(); - } - } - break; - case KeyEvent.KEYCODE_PAGE_UP: - if (handleKeyEvent) { - // Select the first icon on the previous page or the first icon on this page - // if there is no previous page - if (pageIndex > 0) { - parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); - View newIcon = getIconInDirection(layout, parent, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - // Snap to the previous page - workspace.snapToPage(pageIndex - 1); - } - } else { - View newIcon = getIconInDirection(layout, parent, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_PAGE_DOWN: - if (handleKeyEvent) { - // Select the first icon on the next page or the last icon on this page - // if there is no previous page - if (pageIndex < (pageCount - 1)) { - parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); - View newIcon = getIconInDirection(layout, parent, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - // Snap to the next page - workspace.snapToPage(pageIndex + 1); - } - } else { - View newIcon = getIconInDirection(layout, parent, - parent.getChildCount(), -1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_HOME: - if (handleKeyEvent) { - // Select the first icon on this page - View newIcon = getIconInDirection(layout, parent, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_END: - if (handleKeyEvent) { - // Select the last icon on this page - View newIcon = getIconInDirection(layout, parent, - parent.getChildCount(), -1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - wasHandled = true; - break; - default: break; - } - return wasHandled; - } - - /** - * Handles key events for items in a Folder. - */ - static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) { - ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); - final CellLayout layout = (CellLayout) parent.getParent(); - final Folder folder = (Folder) layout.getParent(); - View title = folder.mFolderName; - - final int action = e.getAction(); - final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); - boolean wasHandled = false; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (handleKeyEvent) { - // Select the previous icon - View newIcon = getIconInDirection(layout, parent, v, -1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (handleKeyEvent) { - // Select the next icon - View newIcon = getIconInDirection(layout, parent, v, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - title.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (handleKeyEvent) { - // Select the closest icon in the previous line - View newIcon = getClosestIconOnLine(layout, parent, v, -1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (handleKeyEvent) { - // Select the closest icon in the next line - View newIcon = getClosestIconOnLine(layout, parent, v, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } else { - title.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_HOME: - if (handleKeyEvent) { - // Select the first icon on this page - View newIcon = getIconInDirection(layout, parent, -1, 1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - wasHandled = true; - break; - case KeyEvent.KEYCODE_MOVE_END: - if (handleKeyEvent) { - // Select the last icon on this page - View newIcon = getIconInDirection(layout, parent, - parent.getChildCount(), -1); - if (newIcon != null) { - newIcon.requestFocus(); - } - } - wasHandled = true; - break; - default: break; - } - return wasHandled; - } -} diff --git a/src/com/android/launcher2/FocusOnlyTabWidget.java b/src/com/android/launcher2/FocusOnlyTabWidget.java deleted file mode 100644 index 8e9f58c61..000000000 --- a/src/com/android/launcher2/FocusOnlyTabWidget.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TabWidget; - -public class FocusOnlyTabWidget extends TabWidget { - public FocusOnlyTabWidget(Context context) { - super(context); - } - - public FocusOnlyTabWidget(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public View getSelectedTab() { - final int count = getTabCount(); - for (int i = 0; i < count; ++i) { - View v = getChildTabViewAt(i); - if (v.isSelected()) { - return v; - } - } - return null; - } - - public int getChildTabIndex(View v) { - final int tabCount = getTabCount(); - for (int i = 0; i < tabCount; ++i) { - if (getChildTabViewAt(i) == v) { - return i; - } - } - return -1; - } - - public void setCurrentTabToFocusedTab() { - View tab = null; - int index = -1; - final int count = getTabCount(); - for (int i = 0; i < count; ++i) { - View v = getChildTabViewAt(i); - if (v.hasFocus()) { - tab = v; - index = i; - break; - } - } - if (index > -1) { - super.setCurrentTab(index); - super.onFocusChange(tab, true); - } - } - public void superOnFocusChange(View v, boolean hasFocus) { - super.onFocusChange(v, hasFocus); - } - - @Override - public void onFocusChange(android.view.View v, boolean hasFocus) { - if (v == this && hasFocus && getTabCount() > 0) { - getSelectedTab().requestFocus(); - return; - } - } -} diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java deleted file mode 100644 index de2e4359a..000000000 --- a/src/com/android/launcher2/Folder.java +++ /dev/null @@ -1,1105 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.InputType; -import android.text.Selection; -import android.text.Spannable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ActionMode; -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.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.launcher.R; -import com.android.launcher2.FolderInfo.FolderListener; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * Represents a set of icons chosen by the user or generated by the system. - */ -public class Folder extends LinearLayout implements DragSource, View.OnClickListener, - View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, - View.OnFocusChangeListener { - - @SuppressWarnings("unused") - private static final String TAG = "Launcher.Folder"; - - protected DragController mDragController; - protected Launcher mLauncher; - protected FolderInfo mInfo; - - static final int STATE_NONE = -1; - static final int STATE_SMALL = 0; - static final int STATE_ANIMATING = 1; - static final int STATE_OPEN = 2; - - private int mExpandDuration; - protected CellLayout mContent; - private final LayoutInflater mInflater; - private final IconCache mIconCache; - private int mState = STATE_NONE; - private static final int REORDER_ANIMATION_DURATION = 230; - private static final int ON_EXIT_CLOSE_DELAY = 800; - private boolean mRearrangeOnClose = false; - private FolderIcon mFolderIcon; - private int mMaxCountX; - private int mMaxCountY; - private int mMaxNumItems; - private ArrayList mItemsInReadingOrder = new ArrayList(); - private Drawable mIconDrawable; - boolean mItemsInvalidated = false; - private ShortcutInfo mCurrentDragInfo; - private View mCurrentDragView; - boolean mSuppressOnAdd = false; - private int[] mTargetCell = new int[2]; - private int[] mPreviousTargetCell = new int[2]; - private int[] mEmptyCell = new int[2]; - private Alarm mReorderAlarm = new Alarm(); - private Alarm mOnExitAlarm = new Alarm(); - private int mFolderNameHeight; - private Rect mTempRect = new Rect(); - private boolean mDragInProgress = false; - private boolean mDeleteFolderOnDropCompleted = false; - private boolean mSuppressFolderDeletion = false; - private boolean mItemAddedBackToSelfViaIcon = false; - FolderEditText mFolderName; - private float mFolderIconPivotX; - private float mFolderIconPivotY; - - private boolean mIsEditingName = false; - private InputMethodManager mInputMethodManager; - - private static String sDefaultFolderName; - private static String sHintText; - private ObjectAnimator mOpenCloseAnimator; - - /** - * Used to inflate the Workspace from XML. - * - * @param context The application's context. - * @param attrs The attribtues set containing the Workspace's customization values. - */ - public Folder(Context context, AttributeSet attrs) { - super(context, attrs); - setAlwaysDrawnWithCacheEnabled(false); - mInflater = LayoutInflater.from(context); - mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache(); - - Resources res = getResources(); - mMaxCountX = res.getInteger(R.integer.folder_max_count_x); - mMaxCountY = res.getInteger(R.integer.folder_max_count_y); - mMaxNumItems = res.getInteger(R.integer.folder_max_num_items); - if (mMaxCountX < 0 || mMaxCountY < 0 || mMaxNumItems < 0) { - mMaxCountX = LauncherModel.getCellCountX(); - mMaxCountY = LauncherModel.getCellCountY(); - mMaxNumItems = mMaxCountX * mMaxCountY; - } - - mInputMethodManager = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - - mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration); - - if (sDefaultFolderName == null) { - sDefaultFolderName = res.getString(R.string.folder_name); - } - if (sHintText == null) { - sHintText = res.getString(R.string.folder_hint_text); - } - mLauncher = (Launcher) context; - // We need this view to be focusable in touch mode so that when text editing of the folder - // name is complete, we have something to focus on, thus hiding the cursor and giving - // reliable behvior when clicking the text field (since it will always gain focus on click). - setFocusableInTouchMode(true); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContent = (CellLayout) findViewById(R.id.folder_content); - mContent.setGridSize(0, 0); - mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); - mFolderName = (FolderEditText) findViewById(R.id.folder_name); - mFolderName.setFolder(this); - mFolderName.setOnFocusChangeListener(this); - - // We find out how tall the text view wants to be (it is set to wrap_content), so that - // we can allocate the appropriate amount of space for it. - int measureSpec = MeasureSpec.UNSPECIFIED; - mFolderName.measure(measureSpec, measureSpec); - mFolderNameHeight = mFolderName.getMeasuredHeight(); - - // We disable action mode for now since it messes up the view on phones - mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); - mFolderName.setOnEditorActionListener(this); - mFolderName.setSelectAllOnFocus(true); - mFolderName.setInputType(mFolderName.getInputType() | - InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); - } - - private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return false; - } - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return false; - } - - public void onDestroyActionMode(ActionMode mode) { - } - - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - }; - - public void onClick(View v) { - Object tag = v.getTag(); - if (tag instanceof ShortcutInfo) { - // refactor this code from Folder - ShortcutInfo item = (ShortcutInfo) tag; - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - item.intent.setSourceBounds(new Rect(pos[0], pos[1], - pos[0] + v.getWidth(), pos[1] + v.getHeight())); - - mLauncher.startActivitySafely(v, item.intent, item); - } - } - - public boolean onLongClick(View v) { - // Return if global dragging is not enabled - if (!mLauncher.isDraggingEnabled()) return true; - - Object tag = v.getTag(); - if (tag instanceof ShortcutInfo) { - ShortcutInfo item = (ShortcutInfo) tag; - if (!v.isInTouchMode()) { - return false; - } - - mLauncher.dismissFolderCling(null); - - mLauncher.getWorkspace().onDragStartedWithItem(v); - mLauncher.getWorkspace().beginDragShared(v, this); - mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; - - mCurrentDragInfo = item; - mEmptyCell[0] = item.cellX; - mEmptyCell[1] = item.cellY; - mCurrentDragView = v; - - mContent.removeView(mCurrentDragView); - mInfo.remove(mCurrentDragInfo); - mDragInProgress = true; - mItemAddedBackToSelfViaIcon = false; - } - return true; - } - - public boolean isEditingName() { - return mIsEditingName; - } - - public void startEditingFolderName() { - mFolderName.setHint(""); - mIsEditingName = true; - } - - public void dismissEditingName() { - mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - doneEditingFolderName(true); - } - - public void doneEditingFolderName(boolean commit) { - mFolderName.setHint(sHintText); - // Convert to a string here to ensure that no other state associated with the text field - // gets saved. - String newTitle = mFolderName.getText().toString(); - mInfo.setTitle(newTitle); - LauncherModel.updateItemInDatabase(mLauncher, mInfo); - - if (commit) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - String.format(getContext().getString(R.string.folder_renamed), newTitle)); - } - // In order to clear the focus from the text field, we set the focus on ourself. This - // ensures that every time the field is clicked, focus is gained, giving reliable behavior. - requestFocus(); - - Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); - mIsEditingName = false; - } - - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - dismissEditingName(); - return true; - } - return false; - } - - public View getEditTextRegion() { - return mFolderName; - } - - public Drawable getDragDrawable() { - return mIconDrawable; - } - - /** - * We need to handle touch events to prevent them from falling through to the workspace below. - */ - @Override - public boolean onTouchEvent(MotionEvent ev) { - return true; - } - - public void setDragController(DragController dragController) { - mDragController = dragController; - } - - void setFolderIcon(FolderIcon icon) { - mFolderIcon = icon; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - // When the folder gets focus, we don't want to announce the list of items. - return true; - } - - /** - * @return the FolderInfo object associated with this folder - */ - FolderInfo getInfo() { - return mInfo; - } - - private class GridComparator implements Comparator { - int mNumCols; - public GridComparator(int numCols) { - mNumCols = numCols; - } - - @Override - public int compare(ShortcutInfo lhs, ShortcutInfo rhs) { - int lhIndex = lhs.cellY * mNumCols + lhs.cellX; - int rhIndex = rhs.cellY * mNumCols + rhs.cellX; - return (lhIndex - rhIndex); - } - } - - private void placeInReadingOrder(ArrayList items) { - int maxX = 0; - int count = items.size(); - for (int i = 0; i < count; i++) { - ShortcutInfo item = items.get(i); - if (item.cellX > maxX) { - maxX = item.cellX; - } - } - - GridComparator gridComparator = new GridComparator(maxX + 1); - Collections.sort(items, gridComparator); - final int countX = mContent.getCountX(); - for (int i = 0; i < count; i++) { - int x = i % countX; - int y = i / countX; - ShortcutInfo item = items.get(i); - item.cellX = x; - item.cellY = y; - } - } - - void bind(FolderInfo info) { - mInfo = info; - ArrayList children = info.contents; - ArrayList overflow = new ArrayList(); - setupContentForNumItems(children.size()); - placeInReadingOrder(children); - int count = 0; - for (int i = 0; i < children.size(); i++) { - ShortcutInfo child = (ShortcutInfo) children.get(i); - if (!createAndAddShortcut(child)) { - overflow.add(child); - } else { - count++; - } - } - - // We rearrange the items in case there are any empty gaps - setupContentForNumItems(count); - - // If our folder has too many items we prune them from the list. This is an issue - // when upgrading from the old Folders implementation which could contain an unlimited - // number of items. - for (ShortcutInfo item: overflow) { - mInfo.remove(item); - LauncherModel.deleteItemFromDatabase(mLauncher, item); - } - - mItemsInvalidated = true; - updateTextViewFocus(); - mInfo.addListener(this); - - if (!sDefaultFolderName.contentEquals(mInfo.title)) { - mFolderName.setText(mInfo.title); - } else { - mFolderName.setText(""); - } - updateItemLocationsInDatabase(); - } - - /** - * Creates a new UserFolder, inflated from R.layout.user_folder. - * - * @param context The application's context. - * - * @return A new UserFolder. - */ - static Folder fromXml(Context context) { - return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); - } - - /** - * This method is intended to make the UserFolder to be visually identical in size and position - * to its associated FolderIcon. This allows for a seamless transition into the expanded state. - */ - private void positionAndSizeAsIcon() { - if (!(getParent() instanceof DragLayer)) return; - setScaleX(0.8f); - setScaleY(0.8f); - setAlpha(0f); - mState = STATE_SMALL; - } - - public void animateOpen() { - positionAndSizeAsIcon(); - - if (!(getParent() instanceof DragLayer)) return; - centerAboutIcon(); - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); - final ObjectAnimator oa = mOpenCloseAnimator = - ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); - - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - String.format(getContext().getString(R.string.folder_opened), - mContent.getCountX(), mContent.getCountY())); - mState = STATE_ANIMATING; - } - @Override - public void onAnimationEnd(Animator animation) { - mState = STATE_OPEN; - setLayerType(LAYER_TYPE_NONE, null); - Cling cling = mLauncher.showFirstRunFoldersCling(); - if (cling != null) { - cling.bringToFront(); - } - setFocusOnFirstChild(); - } - }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - buildLayer(); - post(new Runnable() { - public void run() { - // Check if the animator changed in the meantime - if (oa != mOpenCloseAnimator) - return; - oa.start(); - } - }); - } - - private void sendCustomAccessibilityEvent(int type, String text) { - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain(type); - onInitializeAccessibilityEvent(event); - event.getText().add(text); - accessibilityManager.sendAccessibilityEvent(event); - } - } - - private void setFocusOnFirstChild() { - View firstChild = mContent.getChildAt(0, 0); - if (firstChild != null) { - firstChild.requestFocus(); - } - } - - public void animateClosed() { - if (!(getParent() instanceof DragLayer)) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); - final ObjectAnimator oa = mOpenCloseAnimator = - ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); - - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onCloseComplete(); - setLayerType(LAYER_TYPE_NONE, null); - mState = STATE_SMALL; - } - @Override - public void onAnimationStart(Animator animation) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.folder_closed)); - mState = STATE_ANIMATING; - } - }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - buildLayer(); - post(new Runnable() { - public void run() { - // Check if the animator changed in the meantime - if (oa != mOpenCloseAnimator) - return; - oa.start(); - } - }); - } - - void notifyDataSetChanged() { - // recreate all the children if the data set changes under us. We may want to do this more - // intelligently (ie just removing the views that should no longer exist) - mContent.removeAllViewsInLayout(); - bind(mInfo); - } - - public boolean acceptDrop(DragObject d) { - final ItemInfo item = (ItemInfo) d.dragInfo; - final int itemType = item.itemType; - return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || - itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && - !isFull()); - } - - protected boolean findAndSetEmptyCells(ShortcutInfo item) { - int[] emptyCell = new int[2]; - if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { - item.cellX = emptyCell[0]; - item.cellY = emptyCell[1]; - return true; - } else { - return false; - } - } - - protected boolean createAndAddShortcut(ShortcutInfo item) { - final TextView textView = - (TextView) mInflater.inflate(R.layout.application, this, false); - textView.setCompoundDrawablesWithIntrinsicBounds(null, - new FastBitmapDrawable(item.getIcon(mIconCache)), null, null); - textView.setText(item.title); - textView.setTag(item); - - textView.setOnClickListener(this); - textView.setOnLongClickListener(this); - - // We need to check here to verify that the given item's location isn't already occupied - // by another item. - if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0 - || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) { - // This shouldn't happen, log it. - Log.e(TAG, "Folder order not properly persisted during bind"); - if (!findAndSetEmptyCells(item)) { - return false; - } - } - - CellLayout.LayoutParams lp = - new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); - boolean insert = false; - textView.setOnKeyListener(new FolderKeyEventListener()); - mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); - return true; - } - - public void onDragEnter(DragObject d) { - mPreviousTargetCell[0] = -1; - mPreviousTargetCell[1] = -1; - mOnExitAlarm.cancelAlarm(); - } - - OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { - public void onAlarm(Alarm alarm) { - realTimeReorder(mEmptyCell, mTargetCell); - } - }; - - boolean readingOrderGreaterThan(int[] v1, int[] v2) { - if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) { - return true; - } else { - return false; - } - } - - private void realTimeReorder(int[] empty, int[] target) { - boolean wrap; - int startX; - int endX; - int startY; - int delay = 0; - float delayAmount = 30; - if (readingOrderGreaterThan(target, empty)) { - wrap = empty[0] >= mContent.getCountX() - 1; - startY = wrap ? empty[1] + 1 : empty[1]; - for (int y = startY; y <= target[1]; y++) { - startX = y == empty[1] ? empty[0] + 1 : 0; - endX = y < target[1] ? mContent.getCountX() - 1 : target[0]; - for (int x = startX; x <= endX; x++) { - View v = mContent.getChildAt(x,y); - if (mContent.animateChildToPosition(v, empty[0], empty[1], - REORDER_ANIMATION_DURATION, delay, true, true)) { - empty[0] = x; - empty[1] = y; - delay += delayAmount; - delayAmount *= 0.9; - } - } - } - } else { - wrap = empty[0] == 0; - startY = wrap ? empty[1] - 1 : empty[1]; - for (int y = startY; y >= target[1]; y--) { - startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1; - endX = y > target[1] ? 0 : target[0]; - for (int x = startX; x >= endX; x--) { - View v = mContent.getChildAt(x,y); - if (mContent.animateChildToPosition(v, empty[0], empty[1], - REORDER_ANIMATION_DURATION, delay, true, true)) { - empty[0] = x; - empty[1] = y; - delay += delayAmount; - delayAmount *= 0.9; - } - } - } - } - } - - public void onDragOver(DragObject d) { - float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null); - mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell); - - if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) { - mReorderAlarm.cancelAlarm(); - mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); - mReorderAlarm.setAlarm(150); - mPreviousTargetCell[0] = mTargetCell[0]; - mPreviousTargetCell[1] = mTargetCell[1]; - } - } - - // This is used to compute the visual center of the dragView. The idea is that - // the visual center represents the user's interpretation of where the item is, and hence - // is the appropriate point to use when determining drop location. - private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, - DragView dragView, float[] recycle) { - float res[]; - if (recycle == null) { - res = new float[2]; - } else { - res = recycle; - } - - // These represent the visual top and left of drag view if a dragRect was provided. - // If a dragRect was not provided, then they correspond to the actual view left and - // top, as the dragRect is in that case taken to be the entire dragView. - // R.dimen.dragViewOffsetY. - int left = x - xOffset; - int top = y - yOffset; - - // In order to find the visual center, we shift by half the dragRect - res[0] = left + dragView.getDragRegion().width() / 2; - res[1] = top + dragView.getDragRegion().height() / 2; - - return res; - } - - OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { - public void onAlarm(Alarm alarm) { - completeDragExit(); - } - }; - - public void completeDragExit() { - mLauncher.closeFolder(); - mCurrentDragInfo = null; - mCurrentDragView = null; - mSuppressOnAdd = false; - mRearrangeOnClose = true; - } - - public void onDragExit(DragObject d) { - // We only close the folder if this is a true drag exit, ie. not because a drop - // has occurred above the folder. - if (!d.dragComplete) { - mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); - mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); - } - mReorderAlarm.cancelAlarm(); - } - - public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, - boolean success) { - if (success) { - if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) { - replaceFolderWithFinalItem(); - } - } else { - // The drag failed, we need to return the item to the folder - mFolderIcon.onDrop(d); - - // We're going to trigger a "closeFolder" which may occur before this item has - // been added back to the folder -- this could cause the folder to be deleted - if (mOnExitAlarm.alarmPending()) { - mSuppressFolderDeletion = true; - } - } - - if (target != this) { - if (mOnExitAlarm.alarmPending()) { - mOnExitAlarm.cancelAlarm(); - completeDragExit(); - } - } - mDeleteFolderOnDropCompleted = false; - mDragInProgress = false; - mItemAddedBackToSelfViaIcon = false; - mCurrentDragInfo = null; - mCurrentDragView = null; - mSuppressOnAdd = false; - - // Reordering may have occured, and we need to save the new item locations. We do this once - // at the end to prevent unnecessary database operations. - updateItemLocationsInDatabase(); - } - - @Override - public boolean supportsFlingToDelete() { - return true; - } - - public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { - // Do nothing - } - - @Override - public void onFlingToDeleteCompleted() { - // Do nothing - } - - private void updateItemLocationsInDatabase() { - ArrayList list = getItemsInReadingOrder(); - for (int i = 0; i < list.size(); i++) { - View v = list.get(i); - ItemInfo info = (ItemInfo) v.getTag(); - LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0, - info.cellX, info.cellY); - } - } - - public void notifyDrop() { - if (mDragInProgress) { - mItemAddedBackToSelfViaIcon = true; - } - } - - public boolean isDropEnabled() { - return true; - } - - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - - private void setupContentDimensions(int count) { - ArrayList list = getItemsInReadingOrder(); - - int countX = mContent.getCountX(); - int countY = mContent.getCountY(); - boolean done = false; - - while (!done) { - int oldCountX = countX; - int oldCountY = countY; - if (countX * countY < count) { - // Current grid is too small, expand it - if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) { - countX++; - } else if (countY < mMaxCountY) { - countY++; - } - if (countY == 0) countY++; - } else if ((countY - 1) * countX >= count && countY >= countX) { - countY = Math.max(0, countY - 1); - } else if ((countX - 1) * countY >= count) { - countX = Math.max(0, countX - 1); - } - done = countX == oldCountX && countY == oldCountY; - } - mContent.setGridSize(countX, countY); - arrangeChildren(list); - } - - public boolean isFull() { - return getItemCount() >= mMaxNumItems; - } - - private void centerAboutIcon() { - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight() - + mFolderNameHeight; - DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); - - parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect); - - int centerX = mTempRect.centerX(); - int centerY = mTempRect.centerY(); - int centeredLeft = centerX - width / 2; - int centeredTop = centerY - height / 2; - - int currentPage = mLauncher.getWorkspace().getCurrentPage(); - // In case the workspace is scrolling, we need to use the final scroll to compute - // the folders bounds. - mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage); - // We first fetch the currently visible CellLayoutChildren - CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage); - ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets(); - Rect bounds = new Rect(); - parent.getDescendantRectRelativeToSelf(boundingLayout, bounds); - // We reset the workspaces scroll - mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage); - - // We need to bound the folder to the currently visible CellLayoutChildren - int left = Math.min(Math.max(bounds.left, centeredLeft), - bounds.left + bounds.width() - width); - int top = Math.min(Math.max(bounds.top, centeredTop), - bounds.top + bounds.height() - height); - // If the folder doesn't fit within the bounds, center it about the desired bounds - if (width >= bounds.width()) { - left = bounds.left + (bounds.width() - width) / 2; - } - if (height >= bounds.height()) { - top = bounds.top + (bounds.height() - height) / 2; - } - - int folderPivotX = width / 2 + (centeredLeft - left); - int folderPivotY = height / 2 + (centeredTop - top); - setPivotX(folderPivotX); - setPivotY(folderPivotY); - mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * - (1.0f * folderPivotX / width)); - mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * - (1.0f * folderPivotY / height)); - - lp.width = width; - lp.height = height; - lp.x = left; - lp.y = top; - } - - float getPivotXForIconAnimation() { - return mFolderIconPivotX; - } - float getPivotYForIconAnimation() { - return mFolderIconPivotY; - } - - private void setupContentForNumItems(int count) { - setupContentDimensions(count); - - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - if (lp == null) { - lp = new DragLayer.LayoutParams(0, 0); - lp.customPosition = true; - setLayoutParams(lp); - } - centerAboutIcon(); - } - - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight() - + mFolderNameHeight; - - int contentWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(), - MeasureSpec.EXACTLY); - int contentHeightSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredHeight(), - MeasureSpec.EXACTLY); - mContent.measure(contentWidthSpec, contentHeightSpec); - - mFolderName.measure(contentWidthSpec, - MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); - setMeasuredDimension(width, height); - } - - private void arrangeChildren(ArrayList list) { - int[] vacant = new int[2]; - if (list == null) { - list = getItemsInReadingOrder(); - } - mContent.removeAllViews(); - - for (int i = 0; i < list.size(); i++) { - View v = list.get(i); - mContent.getVacantCell(vacant, 1, 1); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); - lp.cellX = vacant[0]; - lp.cellY = vacant[1]; - ItemInfo info = (ItemInfo) v.getTag(); - if (info.cellX != vacant[0] || info.cellY != vacant[1]) { - info.cellX = vacant[0]; - info.cellY = vacant[1]; - LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0, - info.cellX, info.cellY); - } - boolean insert = false; - mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); - } - mItemsInvalidated = true; - } - - public int getItemCount() { - return mContent.getShortcutsAndWidgets().getChildCount(); - } - - public View getItemAt(int index) { - return mContent.getShortcutsAndWidgets().getChildAt(index); - } - - private void onCloseComplete() { - DragLayer parent = (DragLayer) getParent(); - if (parent != null) { - parent.removeView(this); - } - mDragController.removeDropTarget((DropTarget) this); - clearFocus(); - mFolderIcon.requestFocus(); - - if (mRearrangeOnClose) { - setupContentForNumItems(getItemCount()); - mRearrangeOnClose = false; - } - if (getItemCount() <= 1) { - if (!mDragInProgress && !mSuppressFolderDeletion) { - replaceFolderWithFinalItem(); - } else if (mDragInProgress) { - mDeleteFolderOnDropCompleted = true; - } - } - mSuppressFolderDeletion = false; - } - - private void replaceFolderWithFinalItem() { - ItemInfo finalItem = null; - - if (getItemCount() == 1) { - finalItem = mInfo.contents.get(0); - } - - // Remove the folder completely - CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screen); - cellLayout.removeView(mFolderIcon); - if (mFolderIcon instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget) mFolderIcon); - } - mLauncher.removeFolder(mInfo); - - if (finalItem != null) { - LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, - mInfo.screen, mInfo.cellX, mInfo.cellY); - } - LauncherModel.deleteItemFromDatabase(mLauncher, mInfo); - - // Add the last remaining child to the workspace in place of the folder - if (finalItem != null) { - View child = mLauncher.createShortcut(R.layout.application, cellLayout, - (ShortcutInfo) finalItem); - - mLauncher.getWorkspace().addInScreen(child, mInfo.container, mInfo.screen, mInfo.cellX, - mInfo.cellY, mInfo.spanX, mInfo.spanY); - } - } - - // This method keeps track of the last item in the folder for the purposes - // of keyboard focus - private void updateTextViewFocus() { - View lastChild = getItemAt(getItemCount() - 1); - getItemAt(getItemCount() - 1); - if (lastChild != null) { - mFolderName.setNextFocusDownId(lastChild.getId()); - mFolderName.setNextFocusRightId(lastChild.getId()); - mFolderName.setNextFocusLeftId(lastChild.getId()); - mFolderName.setNextFocusUpId(lastChild.getId()); - } - } - - public void onDrop(DragObject d) { - ShortcutInfo item; - if (d.dragInfo instanceof ApplicationInfo) { - // Came from all apps -- make a copy - item = ((ApplicationInfo) d.dragInfo).makeShortcut(); - item.spanX = 1; - item.spanY = 1; - } else { - item = (ShortcutInfo) d.dragInfo; - } - // Dragged from self onto self, currently this is the only path possible, however - // we keep this as a distinct code path. - if (item == mCurrentDragInfo) { - ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag(); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams(); - si.cellX = lp.cellX = mEmptyCell[0]; - si.cellX = lp.cellY = mEmptyCell[1]; - mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true); - if (d.dragView.hasDrawn()) { - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView); - } else { - d.deferDragViewCleanupPostAnimation = false; - mCurrentDragView.setVisibility(VISIBLE); - } - mItemsInvalidated = true; - setupContentDimensions(getItemCount()); - mSuppressOnAdd = true; - } - mInfo.add(item); - } - - public void onAdd(ShortcutInfo item) { - mItemsInvalidated = true; - // If the item was dropped onto this open folder, we have done the work associated - // with adding the item to the folder, as indicated by mSuppressOnAdd being set - if (mSuppressOnAdd) return; - if (!findAndSetEmptyCells(item)) { - // The current layout is full, can we expand it? - setupContentForNumItems(getItemCount() + 1); - findAndSetEmptyCells(item); - } - createAndAddShortcut(item); - LauncherModel.addOrMoveItemInDatabase( - mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); - } - - public void onRemove(ShortcutInfo item) { - mItemsInvalidated = true; - // If this item is being dragged from this open folder, we have already handled - // the work associated with removing the item, so we don't have to do anything here. - if (item == mCurrentDragInfo) return; - View v = getViewForInfo(item); - mContent.removeView(v); - if (mState == STATE_ANIMATING) { - mRearrangeOnClose = true; - } else { - setupContentForNumItems(getItemCount()); - } - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } - } - - private View getViewForInfo(ShortcutInfo item) { - for (int j = 0; j < mContent.getCountY(); j++) { - for (int i = 0; i < mContent.getCountX(); i++) { - View v = mContent.getChildAt(i, j); - if (v.getTag() == item) { - return v; - } - } - } - return null; - } - - public void onItemsChanged() { - updateTextViewFocus(); - } - - public void onTitleChanged(CharSequence title) { - } - - public ArrayList getItemsInReadingOrder() { - return getItemsInReadingOrder(true); - } - - public ArrayList getItemsInReadingOrder(boolean includeCurrentDragItem) { - if (mItemsInvalidated) { - mItemsInReadingOrder.clear(); - for (int j = 0; j < mContent.getCountY(); j++) { - for (int i = 0; i < mContent.getCountX(); i++) { - View v = mContent.getChildAt(i, j); - if (v != null) { - ShortcutInfo info = (ShortcutInfo) v.getTag(); - if (info != mCurrentDragInfo || includeCurrentDragItem) { - mItemsInReadingOrder.add(v); - } - } - } - } - mItemsInvalidated = false; - } - return mItemsInReadingOrder; - } - - public void getLocationInDragLayer(int[] loc) { - mLauncher.getDragLayer().getLocationInDragLayer(this, loc); - } - - public void onFocusChange(View v, boolean hasFocus) { - if (v == mFolderName && hasFocus) { - startEditingFolderName(); - } - } -} diff --git a/src/com/android/launcher2/FolderEditText.java b/src/com/android/launcher2/FolderEditText.java deleted file mode 100644 index 13169bd51..000000000 --- a/src/com/android/launcher2/FolderEditText.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.android.launcher2; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.widget.EditText; - -public class FolderEditText extends EditText { - - private Folder mFolder; - - public FolderEditText(Context context) { - super(context); - } - - public FolderEditText(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public FolderEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void setFolder(Folder folder) { - mFolder = folder; - } - - @Override - public boolean onKeyPreIme(int keyCode, KeyEvent event) { - // Catch the back button on the soft keyboard so that we can just close the activity - if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) { - mFolder.doneEditingFolderName(true); - } - return super.onKeyPreIme(keyCode, event); - } -} diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java deleted file mode 100644 index 4919b57f0..000000000 --- a/src/com/android/launcher2/FolderIcon.java +++ /dev/null @@ -1,631 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.launcher.R; -import com.android.launcher2.DropTarget.DragObject; -import com.android.launcher2.FolderInfo.FolderListener; - -import java.util.ArrayList; - -/** - * An icon that can appear on in the workspace representing an {@link UserFolder}. - */ -public class FolderIcon extends LinearLayout implements FolderListener { - private Launcher mLauncher; - Folder mFolder; - FolderInfo mInfo; - private static boolean sStaticValuesDirty = true; - - private CheckLongPressHelper mLongPressHelper; - - // The number of icons to display in the - private static final int NUM_ITEMS_IN_PREVIEW = 3; - private static final int CONSUMPTION_ANIMATION_DURATION = 100; - private static final int DROP_IN_ANIMATION_DURATION = 400; - private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; - - // The degree to which the inner ring grows when accepting drop - private static final float INNER_RING_GROWTH_FACTOR = 0.15f; - - // The degree to which the outer ring is scaled in its natural state - private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; - - // The amount of vertical spread between items in the stack [0...1] - private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f; - - // The degree to which the item in the back of the stack is scaled [0...1] - // (0 means it's not scaled at all, 1 means it's scaled to nothing) - private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; - - public static Drawable sSharedFolderLeaveBehind = null; - - private ImageView mPreviewBackground; - private BubbleTextView mFolderName; - - FolderRingAnimator mFolderRingAnimator = null; - - // These variables are all associated with the drawing of the preview; they are stored - // as member variables for shared usage and to avoid computation on each frame - private int mIntrinsicIconSize; - private float mBaselineIconScale; - private int mBaselineIconSize; - private int mAvailableSpaceInPreview; - private int mTotalWidth = -1; - private int mPreviewOffsetX; - private int mPreviewOffsetY; - private float mMaxPerspectiveShift; - boolean mAnimating = false; - private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0); - private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0); - - public FolderIcon(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public FolderIcon(Context context) { - super(context); - init(); - } - - private void init() { - mLongPressHelper = new CheckLongPressHelper(this); - } - - public boolean isDropEnabled() { - final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); - final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); - final Workspace workspace = (Workspace) cellLayout.getParent(); - return !workspace.isSmall(); - } - - static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, - FolderInfo folderInfo, IconCache iconCache) { - @SuppressWarnings("all") // suppress dead code warning - final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION; - if (error) { - throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " + - "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " + - "is dependent on this"); - } - - FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); - - icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); - icon.mFolderName.setText(folderInfo.title); - icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); - - icon.setTag(folderInfo); - icon.setOnClickListener(launcher); - icon.mInfo = folderInfo; - icon.mLauncher = launcher; - icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format), - folderInfo.title)); - Folder folder = Folder.fromXml(launcher); - folder.setDragController(launcher.getDragController()); - folder.setFolderIcon(icon); - folder.bind(folderInfo); - icon.mFolder = folder; - - icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); - folderInfo.addListener(icon); - - return icon; - } - - @Override - protected Parcelable onSaveInstanceState() { - sStaticValuesDirty = true; - return super.onSaveInstanceState(); - } - - public static class FolderRingAnimator { - public int mCellX; - public int mCellY; - private CellLayout mCellLayout; - public float mOuterRingSize; - public float mInnerRingSize; - public FolderIcon mFolderIcon = null; - public Drawable mOuterRingDrawable = null; - public Drawable mInnerRingDrawable = null; - public static Drawable sSharedOuterRingDrawable = null; - public static Drawable sSharedInnerRingDrawable = null; - public static int sPreviewSize = -1; - public static int sPreviewPadding = -1; - - private ValueAnimator mAcceptAnimator; - private ValueAnimator mNeutralAnimator; - - public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { - mFolderIcon = folderIcon; - Resources res = launcher.getResources(); - mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); - mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo); - - // We need to reload the static values when configuration changes in case they are - // different in another configuration - if (sStaticValuesDirty) { - sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size); - sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); - sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); - sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo); - sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); - sStaticValuesDirty = false; - } - } - - public void animateToAcceptState() { - if (mNeutralAnimator != null) { - mNeutralAnimator.cancel(); - } - mAcceptAnimator = ValueAnimator.ofFloat(0f, 1f); - mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - - final int previewSize = sPreviewSize; - mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; - mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; - if (mCellLayout != null) { - mCellLayout.invalidate(); - } - } - }); - mAcceptAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (mFolderIcon != null) { - mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); - } - } - }); - mAcceptAnimator.start(); - } - - public void animateToNaturalState() { - if (mAcceptAnimator != null) { - mAcceptAnimator.cancel(); - } - mNeutralAnimator = ValueAnimator.ofFloat(0f, 1f); - mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - - final int previewSize = sPreviewSize; - mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; - mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; - if (mCellLayout != null) { - mCellLayout.invalidate(); - } - } - }); - mNeutralAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCellLayout != null) { - mCellLayout.hideFolderAccept(FolderRingAnimator.this); - } - if (mFolderIcon != null) { - mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); - } - } - }); - mNeutralAnimator.start(); - } - - // Location is expressed in window coordinates - public void getCell(int[] loc) { - loc[0] = mCellX; - loc[1] = mCellY; - } - - // Location is expressed in window coordinates - public void setCell(int x, int y) { - mCellX = x; - mCellY = y; - } - - public void setCellLayout(CellLayout layout) { - mCellLayout = layout; - } - - public float getOuterRingSize() { - return mOuterRingSize; - } - - public float getInnerRingSize() { - return mInnerRingSize; - } - } - - private boolean willAcceptItem(ItemInfo item) { - final int itemType = item.itemType; - return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || - itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && - !mFolder.isFull() && item != mInfo && !mInfo.opened); - } - - public boolean acceptDrop(Object dragInfo) { - final ItemInfo item = (ItemInfo) dragInfo; - return willAcceptItem(item); - } - - public void addItem(ShortcutInfo item) { - mInfo.add(item); - LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); - } - - public void onDragEnter(Object dragInfo) { - if (!willAcceptItem((ItemInfo) dragInfo)) return; - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); - CellLayout layout = (CellLayout) getParent().getParent(); - mFolderRingAnimator.setCell(lp.cellX, lp.cellY); - mFolderRingAnimator.setCellLayout(layout); - mFolderRingAnimator.animateToAcceptState(); - layout.showFolderAccept(mFolderRingAnimator); - } - - public void onDragOver(Object dragInfo) { - } - - public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, - final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, - float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { - - Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1]; - computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), destView.getMeasuredWidth()); - - // This will animate the dragView (srcView) into the new folder - onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); - - // This will animate the first item from it's position as an icon into its - // position as the first item in the preview - animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION); - addItem(destInfo); - } - - public void onDragExit(Object dragInfo) { - onDragExit(); - } - - public void onDragExit() { - mFolderRingAnimator.animateToNaturalState(); - } - - private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, - float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, - DragObject d) { - item.cellX = -1; - item.cellY = -1; - - // Typically, the animateView corresponds to the DragView; however, if this is being done - // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we - // will not have a view to animate - if (animateView != null) { - DragLayer dragLayer = mLauncher.getDragLayer(); - Rect from = new Rect(); - dragLayer.getViewRectRelativeToSelf(animateView, from); - Rect to = finalRect; - if (to == null) { - to = new Rect(); - Workspace workspace = mLauncher.getWorkspace(); - // Set cellLayout and this to it's final state to compute final animation locations - workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); - float scaleX = getScaleX(); - float scaleY = getScaleY(); - setScaleX(1.0f); - setScaleY(1.0f); - scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to); - // Finished computing final animation locations, restore current state - setScaleX(scaleX); - setScaleY(scaleY); - workspace.resetTransitionTransform((CellLayout) getParent().getParent()); - } - - int[] center = new int[2]; - float scale = getLocalCenterForIndex(index, center); - center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); - center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); - - to.offset(center[0] - animateView.getMeasuredWidth() / 2, - center[1] - animateView.getMeasuredHeight() / 2); - - float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f; - - float finalScale = scale * scaleRelativeToDragLayer; - dragLayer.animateView(animateView, from, to, finalAlpha, - 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, - new DecelerateInterpolator(2), new AccelerateInterpolator(2), - postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); - postDelayed(new Runnable() { - public void run() { - addItem(item); - } - }, DROP_IN_ANIMATION_DURATION); - } else { - addItem(item); - } - } - - public void onDrop(DragObject d) { - ShortcutInfo item; - if (d.dragInfo instanceof ApplicationInfo) { - // Came from all apps -- make a copy - item = ((ApplicationInfo) d.dragInfo).makeShortcut(); - } else { - item = (ShortcutInfo) d.dragInfo; - } - mFolder.notifyDrop(); - onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); - } - - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - - private void computePreviewDrawingParams(int drawableSize, int totalSize) { - if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { - mIntrinsicIconSize = drawableSize; - mTotalWidth = totalSize; - - final int previewSize = FolderRingAnimator.sPreviewSize; - final int previewPadding = FolderRingAnimator.sPreviewPadding; - - mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); - // cos(45) = 0.707 + ~= 0.1) = 0.8f - int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); - - int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); - mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); - - mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale); - mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; - - mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; - mPreviewOffsetY = previewPadding; - } - } - - private void computePreviewDrawingParams(Drawable d) { - computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); - } - - class PreviewItemDrawingParams { - PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) { - this.transX = transX; - this.transY = transY; - this.scale = scale; - this.overlayAlpha = overlayAlpha; - } - float transX; - float transY; - float scale; - int overlayAlpha; - Drawable drawable; - } - - private float getLocalCenterForIndex(int index, int[] center) { - mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams); - - mParams.transX += mPreviewOffsetX; - mParams.transY += mPreviewOffsetY; - float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; - float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; - - center[0] = (int) Math.round(offsetX); - center[1] = (int) Math.round(offsetY); - return mParams.scale; - } - - private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, - PreviewItemDrawingParams params) { - index = NUM_ITEMS_IN_PREVIEW - index - 1; - float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); - float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); - - float offset = (1 - r) * mMaxPerspectiveShift; - float scaledSize = scale * mBaselineIconSize; - float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; - - // We want to imagine our coordinates from the bottom left, growing up and to the - // right. This is natural for the x-axis, but for the y-axis, we have to invert things. - float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection); - float transX = offset + scaleOffsetCorrection; - float totalScale = mBaselineIconScale * scale; - final int overlayAlpha = (int) (80 * (1 - r)); - - if (params == null) { - params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); - } else { - params.transX = transX; - params.transY = transY; - params.scale = totalScale; - params.overlayAlpha = overlayAlpha; - } - return params; - } - - private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { - canvas.save(); - canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY); - canvas.scale(params.scale, params.scale); - Drawable d = params.drawable; - - if (d != null) { - d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); - d.setFilterBitmap(true); - d.setColorFilter(Color.argb(params.overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP); - d.draw(canvas); - d.clearColorFilter(); - d.setFilterBitmap(false); - } - canvas.restore(); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - if (mFolder == null) return; - if (mFolder.getItemCount() == 0 && !mAnimating) return; - - ArrayList items = mFolder.getItemsInReadingOrder(false); - Drawable d; - TextView v; - - // Update our drawing parameters if necessary - if (mAnimating) { - computePreviewDrawingParams(mAnimParams.drawable); - } else { - v = (TextView) items.get(0); - d = v.getCompoundDrawables()[1]; - computePreviewDrawingParams(d); - } - - int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); - if (!mAnimating) { - for (int i = nItemsInPreview - 1; i >= 0; i--) { - v = (TextView) items.get(i); - d = v.getCompoundDrawables()[1]; - - mParams = computePreviewItemDrawingParams(i, mParams); - mParams.drawable = d; - drawPreviewItem(canvas, mParams); - } - } else { - drawPreviewItem(canvas, mAnimParams); - } - } - - private void animateFirstItem(final Drawable d, int duration) { - computePreviewDrawingParams(d); - final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); - - final float scale0 = 1.0f; - final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2; - final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2; - mAnimParams.drawable = d; - - ValueAnimator va = ValueAnimator.ofFloat(0f, 1.0f); - va.addUpdateListener(new AnimatorUpdateListener(){ - public void onAnimationUpdate(ValueAnimator animation) { - float progress = (Float) animation.getAnimatedValue(); - - mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); - mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); - mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); - invalidate(); - } - }); - va.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mAnimating = true; - } - @Override - public void onAnimationEnd(Animator animation) { - mAnimating = false; - } - }); - va.setDuration(duration); - va.start(); - } - - public void setTextVisible(boolean visible) { - if (visible) { - mFolderName.setVisibility(VISIBLE); - } else { - mFolderName.setVisibility(INVISIBLE); - } - } - - public boolean getTextVisible() { - return mFolderName.getVisibility() == VISIBLE; - } - - public void onItemsChanged() { - invalidate(); - requestLayout(); - } - - public void onAdd(ShortcutInfo item) { - invalidate(); - requestLayout(); - } - - public void onRemove(ShortcutInfo item) { - invalidate(); - requestLayout(); - } - - public void onTitleChanged(CharSequence title) { - mFolderName.setText(title.toString()); - setContentDescription(String.format(getContext().getString(R.string.folder_name_format), - title)); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Call the superclass onTouchEvent first, because sometimes it changes the state to - // isPressed() on an ACTION_UP - boolean result = super.onTouchEvent(event); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mLongPressHelper.postCheckForLongPress(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mLongPressHelper.cancelLongPress(); - break; - } - return result; - } - - @Override - public void cancelLongPress() { - super.cancelLongPress(); - - mLongPressHelper.cancelLongPress(); - } -} diff --git a/src/com/android/launcher2/FolderInfo.java b/src/com/android/launcher2/FolderInfo.java deleted file mode 100644 index f59707671..000000000 --- a/src/com/android/launcher2/FolderInfo.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import java.util.ArrayList; - -import android.content.ContentValues; - -/** - * Represents a folder containing shortcuts or apps. - */ -class FolderInfo extends ItemInfo { - - /** - * Whether this folder has been opened - */ - boolean opened; - - /** - * The folder name. - */ - CharSequence title; - - /** - * The apps and shortcuts - */ - ArrayList contents = new ArrayList(); - - ArrayList listeners = new ArrayList(); - - FolderInfo() { - itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER; - } - - /** - * Add an app or shortcut - * - * @param item - */ - public void add(ShortcutInfo item) { - contents.add(item); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onAdd(item); - } - itemsChanged(); - } - - /** - * Remove an app or shortcut. Does not change the DB. - * - * @param item - */ - public void remove(ShortcutInfo item) { - contents.remove(item); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onRemove(item); - } - itemsChanged(); - } - - public void setTitle(CharSequence title) { - this.title = title; - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onTitleChanged(title); - } - } - - @Override - void onAddToDatabase(ContentValues values) { - super.onAddToDatabase(values); - values.put(LauncherSettings.Favorites.TITLE, title.toString()); - } - - void addListener(FolderListener listener) { - listeners.add(listener); - } - - void removeListener(FolderListener listener) { - if (listeners.contains(listener)) { - listeners.remove(listener); - } - } - - void itemsChanged() { - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onItemsChanged(); - } - } - - @Override - void unbind() { - super.unbind(); - listeners.clear(); - } - - interface FolderListener { - public void onAdd(ShortcutInfo item); - public void onRemove(ShortcutInfo item); - public void onTitleChanged(CharSequence title); - public void onItemsChanged(); - } -} diff --git a/src/com/android/launcher2/HandleView.java b/src/com/android/launcher2/HandleView.java deleted file mode 100644 index d77138b0a..000000000 --- a/src/com/android/launcher2/HandleView.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; - -import com.android.launcher.R; - -public class HandleView extends ImageView { - private static final int ORIENTATION_HORIZONTAL = 1; - - private Launcher mLauncher; - private int mOrientation = ORIENTATION_HORIZONTAL; - - public HandleView(Context context) { - super(context); - } - - public HandleView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HandleView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HandleView, defStyle, 0); - mOrientation = a.getInt(R.styleable.HandleView_direction, ORIENTATION_HORIZONTAL); - a.recycle(); - - setContentDescription(context.getString(R.string.all_apps_button_label)); - } - - @Override - public View focusSearch(int direction) { - View newFocus = super.focusSearch(direction); - if (newFocus == null && !mLauncher.isAllAppsVisible()) { - final Workspace workspace = mLauncher.getWorkspace(); - workspace.dispatchUnhandledMove(null, direction); - return (mOrientation == ORIENTATION_HORIZONTAL && direction == FOCUS_DOWN) ? - this : workspace; - } - return newFocus; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN && mLauncher.isAllAppsVisible()) { - return false; - } - return super.onTouchEvent(ev); - } - - void setLauncher(Launcher launcher) { - mLauncher = launcher; - } -} diff --git a/src/com/android/launcher2/HolographicImageView.java b/src/com/android/launcher2/HolographicImageView.java deleted file mode 100644 index 9e551e047..000000000 --- a/src/com/android/launcher2/HolographicImageView.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.widget.ImageView; - -public class HolographicImageView extends ImageView { - - private final HolographicViewHelper mHolographicHelper; - - public HolographicImageView(Context context) { - this(context, null); - } - - public HolographicImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HolographicImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - mHolographicHelper = new HolographicViewHelper(context); - } - - void invalidatePressedFocusedStates() { - mHolographicHelper.invalidatePressedFocusedStates(this); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // One time call to generate the pressed/focused state -- must be called after - // measure/layout - mHolographicHelper.generatePressedFocusedStates(this); - } -} diff --git a/src/com/android/launcher2/HolographicLinearLayout.java b/src/com/android/launcher2/HolographicLinearLayout.java deleted file mode 100644 index 0f997d5fa..000000000 --- a/src/com/android/launcher2/HolographicLinearLayout.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.launcher.R; - -public class HolographicLinearLayout extends LinearLayout { - - private final HolographicViewHelper mHolographicHelper; - private ImageView mImageView; - private int mImageViewId; - - public HolographicLinearLayout(Context context) { - this(context, null); - } - - public HolographicLinearLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HolographicLinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HolographicLinearLayout, - defStyle, 0); - mImageViewId = a.getResourceId(R.styleable.HolographicLinearLayout_sourceImageViewId, -1); - a.recycle(); - - setWillNotDraw(false); - mHolographicHelper = new HolographicViewHelper(context); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - if (mImageView != null) { - Drawable d = mImageView.getDrawable(); - if (d instanceof StateListDrawable) { - StateListDrawable sld = (StateListDrawable) d; - sld.setState(getDrawableState()); - } - } - } - - void invalidatePressedFocusedStates() { - mHolographicHelper.invalidatePressedFocusedStates(mImageView); - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // One time call to generate the pressed/focused state -- must be called after - // measure/layout - if (mImageView == null) { - mImageView = (ImageView) findViewById(mImageViewId); - } - mHolographicHelper.generatePressedFocusedStates(mImageView); - } -} diff --git a/src/com/android/launcher2/HolographicOutlineHelper.java b/src/com/android/launcher2/HolographicOutlineHelper.java deleted file mode 100644 index 56d194d4e..000000000 --- a/src/com/android/launcher2/HolographicOutlineHelper.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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(); - private final Paint mAlphaClipPaint = new Paint(); - - public static final int MAX_OUTER_BLUR_RADIUS; - public static final int MIN_OUTER_BLUR_RADIUS; - - private static final BlurMaskFilter sExtraThickOuterBlurMaskFilter; - private static final BlurMaskFilter sThickOuterBlurMaskFilter; - private static final BlurMaskFilter sMediumOuterBlurMaskFilter; - private static final BlurMaskFilter sThinOuterBlurMaskFilter; - private static final BlurMaskFilter sThickInnerBlurMaskFilter; - private static final BlurMaskFilter sExtraThickInnerBlurMaskFilter; - private static final BlurMaskFilter sMediumInnerBlurMaskFilter; - - private static final int THICK = 0; - private static final int MEDIUM = 1; - private static final int EXTRA_THICK = 2; - - static { - final float scale = LauncherApplication.getScreenDensity(); - - MIN_OUTER_BLUR_RADIUS = (int) (scale * 1.0f); - MAX_OUTER_BLUR_RADIUS = (int) (scale * 12.0f); - - sExtraThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 12.0f, BlurMaskFilter.Blur.OUTER); - sThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.OUTER); - sMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER); - sThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER); - sExtraThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.NORMAL); - sThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL); - sMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL); - } - - 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); - MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(180, 255); - mAlphaClipPaint.setMaskFilter(alphaClipTable); - } - - /** - * Returns the interpolated holographic highlight alpha for the effect we want when scrolling - * pages. - */ - public static float highlightAlphaInterpolator(float r) { - float maxAlpha = 0.6f; - return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f); - } - - /** - * Returns the interpolated view alpha for the effect we want when scrolling pages. - */ - public static float viewAlphaInterpolator(float r) { - final float pivot = 0.95f; - if (r < pivot) { - return (float) Math.pow(r / pivot, 1.5f); - } else { - return 1.0f; - } - } - - /** - * 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, int thickness) { - applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, mAlphaClipPaint, - thickness); - } - void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor, Paint alphaClipPaint, int thickness) { - - // We start by removing most of the alpha channel so as to ignore shadows, and - // other types of partial transparency when defining the shape of the object - if (alphaClipPaint == null) { - alphaClipPaint = mAlphaClipPaint; - } - Bitmap glowShape = srcDst.extractAlpha(alphaClipPaint, mTempOffset); - - // calculate the outer blur first - BlurMaskFilter outerBlurMaskFilter; - switch (thickness) { - case EXTRA_THICK: - outerBlurMaskFilter = sExtraThickOuterBlurMaskFilter; - break; - case THICK: - outerBlurMaskFilter = sThickOuterBlurMaskFilter; - break; - case MEDIUM: - outerBlurMaskFilter = sMediumOuterBlurMaskFilter; - break; - default: - throw new RuntimeException("Invalid blur thickness"); - } - mBlurPaint.setMaskFilter(outerBlurMaskFilter); - int[] outerBlurOffset = new int[2]; - Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset); - if (thickness == EXTRA_THICK) { - mBlurPaint.setMaskFilter(sMediumOuterBlurMaskFilter); - } else { - mBlurPaint.setMaskFilter(sThinOuterBlurMaskFilter); - } - - int[] brightOutlineOffset = new int[2]; - Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset); - - // calculate the inner blur - srcDstCanvas.setBitmap(glowShape); - srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); - BlurMaskFilter innerBlurMaskFilter; - switch (thickness) { - case EXTRA_THICK: - innerBlurMaskFilter = sExtraThickInnerBlurMaskFilter; - break; - case THICK: - innerBlurMaskFilter = sThickInnerBlurMaskFilter; - break; - case MEDIUM: - innerBlurMaskFilter = sMediumInnerBlurMaskFilter; - break; - default: - throw new RuntimeException("Invalid blur thickness"); - } - mBlurPaint.setMaskFilter(innerBlurMaskFilter); - int[] thickInnerBlurOffset = new int[2]; - Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset); - - // mask out the inner blur - srcDstCanvas.setBitmap(thickInnerBlur); - srcDstCanvas.drawBitmap(glowShape, -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(0, 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(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], - mHolographicPaint); - - // cleanup - srcDstCanvas.setBitmap(null); - brightOutline.recycle(); - thickOuterBlur.recycle(); - thickInnerBlur.recycle(); - glowShape.recycle(); - } - - void applyExtraThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor) { - applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, EXTRA_THICK); - } - - void applyThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor) { - applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, THICK); - } - - void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor, Paint alphaClipPaint) { - applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, alphaClipPaint, - MEDIUM); - } - - void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, - int outlineColor) { - applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, MEDIUM); - } - -} diff --git a/src/com/android/launcher2/HolographicViewHelper.java b/src/com/android/launcher2/HolographicViewHelper.java deleted file mode 100644 index fd499082f..000000000 --- a/src/com/android/launcher2/HolographicViewHelper.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.drawable.StateListDrawable; -import android.widget.ImageView; - -public class HolographicViewHelper { - - private final Canvas mTempCanvas = new Canvas(); - - private boolean mStatesUpdated; - private int mHighlightColor; - - public HolographicViewHelper(Context context) { - Resources res = context.getResources(); - mHighlightColor = res.getColor(android.R.color.holo_blue_light); - } - - /** - * Generate the pressed/focused states if necessary. - */ - void generatePressedFocusedStates(ImageView v) { - if (!mStatesUpdated && v != null) { - mStatesUpdated = true; - Bitmap outline = createPressImage(v, mTempCanvas); - FastBitmapDrawable d = new FastBitmapDrawable(outline); - - StateListDrawable states = new StateListDrawable(); - states.addState(new int[] {android.R.attr.state_pressed}, d); - states.addState(new int[] {android.R.attr.state_focused}, d); - states.addState(new int[] {}, v.getDrawable()); - v.setImageDrawable(states); - } - } - - /** - * Invalidates the pressed/focused states. - */ - void invalidatePressedFocusedStates(ImageView v) { - mStatesUpdated = false; - if (v != null) { - v.invalidate(); - } - } - - /** - * Creates a new press state image which is the old image with a blue overlay. - * Responsibility for the bitmap is transferred to the caller. - */ - private Bitmap createPressImage(ImageView v, Canvas canvas) { - final int padding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; - final Bitmap b = Bitmap.createBitmap( - v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); - - canvas.setBitmap(b); - canvas.save(); - v.getDrawable().draw(canvas); - canvas.restore(); - canvas.drawColor(mHighlightColor, PorterDuff.Mode.SRC_IN); - canvas.setBitmap(null); - - return b; - } -} diff --git a/src/com/android/launcher2/Hotseat.java b/src/com/android/launcher2/Hotseat.java deleted file mode 100644 index 15d606d8f..000000000 --- a/src/com/android/launcher2/Hotseat.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; - -import com.android.launcher.R; - -public class Hotseat extends FrameLayout { - @SuppressWarnings("unused") - private static final String TAG = "Hotseat"; - - private Launcher mLauncher; - private CellLayout mContent; - - private int mCellCountX; - private int mCellCountY; - private int mAllAppsButtonRank; - private boolean mIsLandscape; - - public Hotseat(Context context) { - this(context, null); - } - - public Hotseat(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public Hotseat(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.Hotseat, defStyle, 0); - mCellCountX = a.getInt(R.styleable.Hotseat_cellCountX, -1); - mCellCountY = a.getInt(R.styleable.Hotseat_cellCountY, -1); - mAllAppsButtonRank = context.getResources().getInteger(R.integer.hotseat_all_apps_index); - mIsLandscape = context.getResources().getConfiguration().orientation == - Configuration.ORIENTATION_LANDSCAPE; - } - - public void setup(Launcher launcher) { - mLauncher = launcher; - setOnKeyListener(new HotseatIconKeyEventListener()); - } - - CellLayout getLayout() { - return mContent; - } - - /* Get the orientation invariant order of the item in the hotseat for persistence. */ - int getOrderInHotseat(int x, int y) { - return mIsLandscape ? (mContent.getCountY() - y - 1) : x; - } - /* Get the orientation specific coordinates given an invariant order in the hotseat. */ - int getCellXFromOrder(int rank) { - return mIsLandscape ? 0 : rank; - } - int getCellYFromOrder(int rank) { - return mIsLandscape ? (mContent.getCountY() - (rank + 1)) : 0; - } - public boolean isAllAppsButtonRank(int rank) { - return rank == mAllAppsButtonRank; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - if (mCellCountX < 0) mCellCountX = LauncherModel.getCellCountX(); - if (mCellCountY < 0) mCellCountY = LauncherModel.getCellCountY(); - mContent = (CellLayout) findViewById(R.id.layout); - mContent.setGridSize(mCellCountX, mCellCountY); - mContent.setIsHotseat(true); - - resetLayout(); - } - - void resetLayout() { - mContent.removeAllViewsInLayout(); - - // Add the Apps button - Context context = getContext(); - LayoutInflater inflater = LayoutInflater.from(context); - BubbleTextView allAppsButton = (BubbleTextView) - inflater.inflate(R.layout.application, mContent, false); - allAppsButton.setCompoundDrawablesWithIntrinsicBounds(null, - context.getResources().getDrawable(R.drawable.all_apps_button_icon), null, null); - allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label)); - allAppsButton.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mLauncher != null && - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { - mLauncher.onTouchDownAllAppsButton(v); - } - return false; - } - }); - - allAppsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(android.view.View v) { - if (mLauncher != null) { - mLauncher.onClickAllAppsButton(v); - } - } - }); - - // Note: We do this to ensure that the hotseat is always laid out in the orientation of - // the hotseat in order regardless of which orientation they were added - int x = getCellXFromOrder(mAllAppsButtonRank); - int y = getCellYFromOrder(mAllAppsButtonRank); - CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1); - lp.canReorder = false; - mContent.addViewToCellLayout(allAppsButton, -1, 0, lp, true); - } -} diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java deleted file mode 100644 index aa19545bd..000000000 --- a/src/com/android/launcher2/IconCache.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -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 java.util.HashMap; - -/** - * Cache of application icons. Icons can be made from any thread. - */ -public class IconCache { - @SuppressWarnings("unused") - private static final String TAG = "Launcher.IconCache"; - - private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - - private static class CacheEntry { - public Bitmap icon; - public String title; - } - - private final Bitmap mDefaultIcon; - private final LauncherApplication mContext; - private final PackageManager mPackageManager; - private final HashMap mCache = - new HashMap(INITIAL_ICON_CACHE_CAPACITY); - private int mIconDpi; - - public IconCache(LauncherApplication context) { - ActivityManager activityManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - mContext = context; - mPackageManager = context.getPackageManager(); - mIconDpi = activityManager.getLauncherLargeIconDensity(); - - // need to set mIconDpi before getting default icon - mDefaultIcon = makeDefaultIcon(); - } - - public Drawable getFullResDefaultActivityIcon() { - return getFullResIcon(Resources.getSystem(), - android.R.mipmap.sym_def_app_icon); - } - - public Drawable getFullResIcon(Resources resources, int iconId) { - Drawable d; - try { - d = resources.getDrawableForDensity(iconId, mIconDpi); - } catch (Resources.NotFoundException e) { - d = null; - } - - return (d != null) ? d : getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(String packageName, int iconId) { - Resources resources; - try { - resources = mPackageManager.getResourcesForApplication(packageName); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - return getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(ResolveInfo info) { - return getFullResIcon(info.activityInfo); - } - - public Drawable getFullResIcon(ActivityInfo info) { - - Resources resources; - try { - resources = mPackageManager.getResourcesForApplication( - info.applicationInfo); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - int iconId = info.getIconResource(); - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - return getFullResDefaultActivityIcon(); - } - - private Bitmap makeDefaultIcon() { - Drawable d = getFullResDefaultActivityIcon(); - Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1), - Math.max(d.getIntrinsicHeight(), 1), - Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - d.setBounds(0, 0, b.getWidth(), b.getHeight()); - d.draw(c); - c.setBitmap(null); - return b; - } - - /** - * Remove any records for the supplied ComponentName. - */ - public void remove(ComponentName componentName) { - synchronized (mCache) { - mCache.remove(componentName); - } - } - - /** - * Empty out the cache. - */ - public void flush() { - synchronized (mCache) { - mCache.clear(); - } - } - - /** - * Fill in "application" with the icon and label for "info." - */ - public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info, - HashMap labelCache) { - synchronized (mCache) { - CacheEntry entry = cacheLocked(application.componentName, info, labelCache); - - application.title = entry.title; - application.iconBitmap = entry.icon; - } - } - - public Bitmap getIcon(Intent intent) { - synchronized (mCache) { - final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0); - ComponentName component = intent.getComponent(); - - if (resolveInfo == null || component == null) { - return mDefaultIcon; - } - - CacheEntry entry = cacheLocked(component, resolveInfo, null); - return entry.icon; - } - } - - public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo, - HashMap labelCache) { - synchronized (mCache) { - if (resolveInfo == null || component == null) { - return null; - } - - CacheEntry entry = cacheLocked(component, resolveInfo, labelCache); - return entry.icon; - } - } - - public boolean isDefaultIcon(Bitmap icon) { - return mDefaultIcon == icon; - } - - private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info, - HashMap labelCache) { - CacheEntry entry = mCache.get(componentName); - if (entry == null) { - entry = new CacheEntry(); - - mCache.put(componentName, entry); - - ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info); - if (labelCache != null && labelCache.containsKey(key)) { - entry.title = labelCache.get(key).toString(); - } else { - entry.title = info.loadLabel(mPackageManager).toString(); - if (labelCache != null) { - labelCache.put(key, entry.title); - } - } - if (entry.title == null) { - entry.title = info.activityInfo.name; - } - - entry.icon = Utilities.createIconBitmap( - getFullResIcon(info), mContext); - } - return entry; - } - - public HashMap getAllIcons() { - synchronized (mCache) { - HashMap set = new HashMap(); - for (ComponentName cn : mCache.keySet()) { - final CacheEntry e = mCache.get(cn); - set.put(cn, e.icon); - } - return set; - } - } -} diff --git a/src/com/android/launcher2/InfoDropTarget.java b/src/com/android/launcher2/InfoDropTarget.java deleted file mode 100644 index d6bf5f2cc..000000000 --- a/src/com/android/launcher2/InfoDropTarget.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.ComponentName; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.TransitionDrawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -import com.android.launcher.R; - -public class InfoDropTarget extends ButtonDropTarget { - - private ColorStateList mOriginalTextColor; - private TransitionDrawable mDrawable; - - public InfoDropTarget(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public InfoDropTarget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mOriginalTextColor = getTextColors(); - - // Get the hover color - Resources r = getResources(); - mHoverColor = r.getColor(R.color.info_target_hover_tint); - mDrawable = (TransitionDrawable) getCurrentDrawable(); - mDrawable.setCrossFadeEnabled(true); - - // Remove the text in the Phone UI in landscape - int orientation = getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - if (!LauncherApplication.isScreenLarge()) { - setText(""); - } - } - } - - private boolean isFromAllApps(DragSource source) { - return (source instanceof AppsCustomizePagedView); - } - - @Override - public boolean acceptDrop(DragObject d) { - // 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 drag source. - ComponentName componentName = null; - if (d.dragInfo instanceof ApplicationInfo) { - componentName = ((ApplicationInfo) d.dragInfo).componentName; - } else if (d.dragInfo instanceof ShortcutInfo) { - componentName = ((ShortcutInfo) d.dragInfo).intent.getComponent(); - } else if (d.dragInfo instanceof PendingAddItemInfo) { - componentName = ((PendingAddItemInfo) d.dragInfo).componentName; - } - if (componentName != null) { - mLauncher.startApplicationDetailsActivity(componentName); - } - - // There is no post-drop animation, so clean up the DragView now - d.deferDragViewCleanupPostAnimation = false; - return false; - } - - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - boolean isVisible = true; - - // Hide this button unless we are dragging something from AllApps - if (!isFromAllApps(source)) { - isVisible = false; - } - - mActive = isVisible; - mDrawable.resetTransition(); - setTextColor(mOriginalTextColor); - ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE); - } - - @Override - public void onDragEnd() { - super.onDragEnd(); - mActive = false; - } - - public void onDragEnter(DragObject d) { - super.onDragEnter(d); - - mDrawable.startTransition(mTransitionDuration); - setTextColor(mHoverColor); - } - - public void onDragExit(DragObject d) { - super.onDragExit(d); - - if (!d.dragComplete) { - mDrawable.resetTransition(); - setTextColor(mOriginalTextColor); - } - } -} diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java deleted file mode 100644 index a525d00ee..000000000 --- a/src/com/android/launcher2/InstallShortcutReceiver.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.os.Debug; -import android.widget.Toast; - -import com.android.launcher.R; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -public class InstallShortcutReceiver extends BroadcastReceiver { - public static final String ACTION_INSTALL_SHORTCUT = - "com.android.launcher.action.INSTALL_SHORTCUT"; - public static final String NEW_APPS_PAGE_KEY = "apps.new.page"; - public static final String NEW_APPS_LIST_KEY = "apps.new.list"; - - public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; - public static final int NEW_SHORTCUT_STAGGER_DELAY = 75; - - private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0; - private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1; - private static final int INSTALL_SHORTCUT_NO_SPACE = -2; - - // A mime-type representing shortcut data - public static final String SHORTCUT_MIMETYPE = - "com.android.launcher/shortcut"; - - // The set of shortcuts that are pending install - private static ArrayList mInstallQueue = - new ArrayList(); - - // Determines whether to defer installing shortcuts immediately until - // processAllPendingInstalls() is called. - private static boolean mUseInstallQueue = false; - - private static class PendingInstallShortcutInfo { - Intent data; - Intent launchIntent; - String name; - - public PendingInstallShortcutInfo(Intent rawData, String shortcutName, - Intent shortcutIntent) { - data = rawData; - name = shortcutName; - launchIntent = shortcutIntent; - } - } - - public void onReceive(Context context, Intent data) { - if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { - return; - } - - Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); - if (intent == null) { - return; - } - // This name is only used for comparisons and notifications, so fall back to activity name - // if not supplied - String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - if (name == null) { - try { - PackageManager pm = context.getPackageManager(); - ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0); - name = info.loadLabel(pm).toString(); - } catch (PackageManager.NameNotFoundException nnfe) { - return; - } - } - // Queue the item up for adding if launcher has not loaded properly yet - boolean launcherNotLoaded = LauncherModel.getCellCountX() <= 0 || - LauncherModel.getCellCountY() <= 0; - - PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent); - if (mUseInstallQueue || launcherNotLoaded) { - mInstallQueue.add(info); - } else { - processInstallShortcut(context, info); - } - } - - static void enableInstallQueue() { - mUseInstallQueue = true; - } - static void disableAndFlushInstallQueue(Context context) { - mUseInstallQueue = false; - flushInstallQueue(context); - } - static void flushInstallQueue(Context context) { - Iterator iter = mInstallQueue.iterator(); - while (iter.hasNext()) { - processInstallShortcut(context, iter.next()); - iter.remove(); - } - } - - private static void processInstallShortcut(Context context, - PendingInstallShortcutInfo pendingInfo) { - String spKey = LauncherApplication.getSharedPreferencesKey(); - SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); - - final Intent data = pendingInfo.data; - final Intent intent = pendingInfo.launchIntent; - final String name = pendingInfo.name; - - // Lock on the app so that we don't try and get the items while apps are being added - LauncherApplication app = (LauncherApplication) context.getApplicationContext(); - final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL}; - boolean found = false; - synchronized (app) { - final ArrayList items = LauncherModel.getItemsInLocalCoordinates(context); - final boolean exists = LauncherModel.shortcutExists(context, name, intent); - - // Try adding to the workspace screens incrementally, starting at the default or center - // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1)) - final int screen = Launcher.DEFAULT_SCREEN; - for (int i = 0; i < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) { - int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1); - if (0 <= si && si < Launcher.SCREEN_COUNT) { - found = installShortcut(context, data, items, name, intent, si, exists, sp, - result); - } - } - } - - // We only report error messages (duplicate shortcut or out of space) as the add-animation - // will provide feedback otherwise - if (!found) { - if (result[0] == INSTALL_SHORTCUT_NO_SPACE) { - Toast.makeText(context, context.getString(R.string.completely_out_of_space), - Toast.LENGTH_SHORT).show(); - } else if (result[0] == INSTALL_SHORTCUT_IS_DUPLICATE) { - Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name), - Toast.LENGTH_SHORT).show(); - } - } - } - - private static boolean installShortcut(Context context, Intent data, ArrayList items, - String name, Intent intent, final int screen, boolean shortcutExists, - final SharedPreferences sharedPrefs, int[] result) { - int[] tmpCoordinates = new int[2]; - if (findEmptyCell(context, items, tmpCoordinates, screen)) { - if (intent != null) { - if (intent.getAction() == null) { - intent.setAction(Intent.ACTION_VIEW); - } else if (intent.getAction().equals(Intent.ACTION_MAIN) && - intent.getCategories() != null && - intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { - intent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - } - - // By default, we allow for duplicate entries (located in - // different places) - boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true); - if (duplicate || !shortcutExists) { - // If the new app is going to fall into the same page as before, then just - // continue adding to the current page - int newAppsScreen = sharedPrefs.getInt(NEW_APPS_PAGE_KEY, screen); - Set newApps = new HashSet(); - if (newAppsScreen == screen) { - newApps = sharedPrefs.getStringSet(NEW_APPS_LIST_KEY, newApps); - } - synchronized (newApps) { - newApps.add(intent.toUri(0).toString()); - } - final Set savedNewApps = newApps; - new Thread("setNewAppsThread") { - public void run() { - synchronized (savedNewApps) { - sharedPrefs.edit() - .putInt(NEW_APPS_PAGE_KEY, screen) - .putStringSet(NEW_APPS_LIST_KEY, savedNewApps) - .commit(); - } - } - }.start(); - - // Update the Launcher db - LauncherApplication app = (LauncherApplication) context.getApplicationContext(); - ShortcutInfo info = app.getModel().addShortcut(context, data, - LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, - tmpCoordinates[0], tmpCoordinates[1], true); - if (info == null) { - return false; - } - } else { - result[0] = INSTALL_SHORTCUT_IS_DUPLICATE; - } - - return true; - } - } else { - result[0] = INSTALL_SHORTCUT_NO_SPACE; - } - - return false; - } - - private static boolean findEmptyCell(Context context, ArrayList items, int[] xy, - int screen) { - final int xCount = LauncherModel.getCellCountX(); - final int yCount = LauncherModel.getCellCountY(); - boolean[][] occupied = new boolean[xCount][yCount]; - - ItemInfo item = null; - int cellX, cellY, spanX, spanY; - for (int i = 0; i < items.size(); ++i) { - item = items.get(i); - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (item.screen == screen) { - cellX = item.cellX; - cellY = item.cellY; - spanX = item.spanX; - spanY = item.spanY; - for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) { - for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) { - occupied[x][y] = true; - } - } - } - } - } - - 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 deleted file mode 100644 index a1e9b1187..000000000 --- a/src/com/android/launcher2/InstallWidgetReceiver.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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 mActivities; - private CellLayout mTargetLayout; - private int mTargetLayoutScreen; - private int[] mTargetLayoutPos; - - public WidgetListAdapter(Launcher l, String mimeType, ClipData data, - List 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 AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo; - - final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType, - mClipData); - mLauncher.addAppWidgetFromDrop(createInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mTargetLayoutScreen, null, null, mTargetLayoutPos); - } - } -} diff --git a/src/com/android/launcher2/InterruptibleInOutAnimator.java b/src/com/android/launcher2/InterruptibleInOutAnimator.java deleted file mode 100644 index 135fa3996..000000000 --- a/src/com/android/launcher2/InterruptibleInOutAnimator.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.ValueAnimator; - -/** - * 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 AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(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 deleted file mode 100644 index dedc0f4f3..000000000 --- a/src/com/android/launcher2/ItemInfo.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.ContentValues; -import android.content.Intent; -import android.graphics.Bitmap; -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * Represents an item in the launcher. - */ -class ItemInfo { - - static final int NO_ID = -1; - - /** - * The id in the settings database for this item - */ - long id = NO_ID; - - /** - * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER}, or - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}. - */ - int itemType; - - /** - * The id of the container that holds this item. For the desktop, this will be - * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it - * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders - * it will be the id of the folder. - */ - long container = NO_ID; - - /** - * Iindicates the screen in which the shortcut appears. - */ - int screen = -1; - - /** - * Indicates the X position of the associated cell. - */ - int cellX = -1; - - /** - * Indicates the Y position of the associated cell. - */ - int cellY = -1; - - /** - * Indicates the X cell span. - */ - int spanX = 1; - - /** - * Indicates the Y cell span. - */ - int spanY = 1; - - /** - * Indicates the minimum X cell span. - */ - int minSpanX = 1; - - /** - * Indicates the minimum Y cell span. - */ - int minSpanY = 1; - /** - * Indicates whether the item is a gesture. - */ - boolean isGesture = false; - - /** - * The position of the item in a drag-and-drop operation. - */ - int[] dropPos = null; - - ItemInfo() { - } - - ItemInfo(ItemInfo info) { - id = info.id; - cellX = info.cellX; - cellY = info.cellY; - spanX = info.spanX; - spanY = info.spanY; - screen = info.screen; - itemType = info.itemType; - container = info.container; - } - - /** Returns the package name that the intent will resolve to, or an empty string if - * none exists. */ - static String getPackageName(Intent intent) { - if (intent != null) { - String packageName = intent.getPackage(); - if (packageName == null && intent.getComponent() != null) { - packageName = intent.getComponent().getPackageName(); - } - if (packageName != null) { - return packageName; - } - } - return ""; - } - - /** - * Write the fields of this item to the DB - * - * @param values - */ - void onAddToDatabase(ContentValues values) { - values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType); - if (!isGesture) { - values.put(LauncherSettings.Favorites.CONTAINER, container); - values.put(LauncherSettings.Favorites.SCREEN, screen); - values.put(LauncherSettings.Favorites.CELLX, cellX); - values.put(LauncherSettings.Favorites.CELLY, cellY); - values.put(LauncherSettings.Favorites.SPANX, spanX); - values.put(LauncherSettings.Favorites.SPANY, spanY); - } - } - - 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. - int size = bitmap.getWidth() * bitmap.getHeight() * 4; - ByteArrayOutputStream out = new ByteArrayOutputStream(size); - try { - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.flush(); - out.close(); - return out.toByteArray(); - } catch (IOException e) { - Log.w("Favorite", "Could not write icon"); - return null; - } - } - - static void writeBitmap(ContentValues values, Bitmap bitmap) { - if (bitmap != null) { - byte[] data = flattenBitmap(bitmap); - values.put(LauncherSettings.Favorites.ICON, data); - } - } - - /** - * It is very important that sub-classes implement this if they contain any references - * to the activity (anything in the view hierarchy etc.). If not, leaks can result since - * ItemInfo objects persist across rotation and can hence leak by holding stale references - * to the old view hierarchy / activity. - */ - void unbind() { - } - - @Override - public String toString() { - return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container - + " screen=" + screen + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + " isGesture=" + isGesture + " dropPos=" + dropPos + ")"; - } -} diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java deleted file mode 100644 index f4180cd00..000000000 --- a/src/com/android/launcher2/Launcher.java +++ /dev/null @@ -1,3710 +0,0 @@ - -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.app.SearchManager; -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ComponentCallbacks2; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.os.StrictMode; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.provider.Settings; -import android.speech.RecognizerIntent; -import android.text.Selection; -import android.text.SpannableStringBuilder; -import android.text.method.TextKeyListener; -import android.util.Log; -import android.view.Display; -import android.view.HapticFeedbackConstants; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.view.inputmethod.InputMethodManager; -import android.widget.Advanceable; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.android.common.Search; -import com.android.launcher.R; -import com.android.launcher2.DropTarget.DragObject; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Default launcher application. - */ -public final class Launcher extends Activity - implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, - AllAppsView.Watcher, View.OnTouchListener { - static final String TAG = "Launcher"; - static final boolean LOGD = false; - - static final boolean PROFILE_STARTUP = false; - static final boolean DEBUG_WIDGETS = false; - static final boolean DEBUG_STRICT_MODE = false; - - private static final int MENU_GROUP_WALLPAPER = 1; - private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1; - private static final int MENU_MANAGE_APPS = MENU_WALLPAPER_SETTINGS + 1; - private static final int MENU_SYSTEM_SETTINGS = MENU_MANAGE_APPS + 1; - private static final int MENU_HELP = MENU_SYSTEM_SETTINGS + 1; - - private static final int REQUEST_CREATE_SHORTCUT = 1; - private static final int REQUEST_CREATE_APPWIDGET = 5; - private static final int REQUEST_PICK_APPLICATION = 6; - private static final int REQUEST_PICK_SHORTCUT = 7; - private static final int REQUEST_PICK_APPWIDGET = 9; - private static final int REQUEST_PICK_WALLPAPER = 10; - - private static final int REQUEST_BIND_APPWIDGET = 11; - - static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; - - static final int SCREEN_COUNT = 5; - static final int DEFAULT_SCREEN = 2; - - private static final String PREFERENCES = "launcher.preferences"; - static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher.force_enable_rotation"; - - // The Intent extra that defines whether to ignore the launch animation - static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = - "com.android.launcher.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION"; - - // Type: int - private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; - // Type: int - private static final String RUNTIME_STATE = "launcher.state"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y"; - // 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"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y"; - // Type: parcelable - private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info"; - - private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; - private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME = - "com.android.launcher.toolbar_search_icon"; - private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME = - "com.android.launcher.toolbar_voice_search_icon"; - - /** The different states that Launcher can be in. */ - private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED }; - private State mState = State.WORKSPACE; - private AnimatorSet mStateAnimation; - private AnimatorSet mDividerAnimator; - - static final int APPWIDGET_HOST_ID = 1024; - private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300; - private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600; - private static final int SHOW_CLING_DURATION = 550; - private static final int DISMISS_CLING_DURATION = 250; - - private static final Object sLock = new Object(); - private static int sScreen = DEFAULT_SCREEN; - - // How long to wait before the new-shortcut animation automatically pans the workspace - private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 10; - - private final BroadcastReceiver mCloseSystemDialogsReceiver - = new CloseSystemDialogsIntentReceiver(); - private final ContentObserver mWidgetObserver = new AppWidgetResetObserver(); - - private LayoutInflater mInflater; - - private Workspace mWorkspace; - private View mQsbDivider; - private View mDockDivider; - private DragLayer mDragLayer; - private DragController mDragController; - - private AppWidgetManager mAppWidgetManager; - private LauncherAppWidgetHost mAppWidgetHost; - - private ItemInfo mPendingAddInfo = new ItemInfo(); - private AppWidgetProviderInfo mPendingAddWidgetInfo; - - private int[] mTmpAddItemCellCoordinates = new int[2]; - - private FolderInfo mFolderInfo; - - private Hotseat mHotseat; - private View mAllAppsButton; - - private SearchDropTargetBar mSearchDropTargetBar; - private AppsCustomizeTabHost mAppsCustomizeTabHost; - private AppsCustomizePagedView mAppsCustomizeContent; - private boolean mAutoAdvanceRunning = false; - - private Bundle mSavedState; - - private SpannableStringBuilder mDefaultKeySsb = null; - - private boolean mWorkspaceLoading = true; - - private boolean mPaused = true; - private boolean mRestoring; - private boolean mWaitingForResult; - private boolean mOnResumeNeedsLoad; - - private Bundle mSavedInstanceState; - - private LauncherModel mModel; - private IconCache mIconCache; - private boolean mUserPresent = true; - private boolean mVisible = false; - private boolean mAttached = false; - - private static LocaleConfiguration sLocaleConfiguration = null; - - private static HashMap sFolders = new HashMap(); - - private Intent mAppMarketIntent = null; - - // Related to the auto-advancing of widgets - private final int ADVANCE_MSG = 1; - private final int mAdvanceInterval = 20000; - private final int mAdvanceStagger = 250; - private long mAutoAdvanceSentTime; - private long mAutoAdvanceTimeLeft = -1; - private HashMap mWidgetsToAdvance = - new HashMap(); - - // Determines how long to wait after a rotation before restoring the screen orientation to - // match the sensor state. - private final int mRestoreScreenOrientationDelay = 500; - - // External icons saved in case of resource changes, orientation, etc. - private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2]; - private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2]; - private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2]; - - static final ArrayList sDumpLogs = new ArrayList(); - - // We only want to get the SharedPreferences once since it does an FS stat each time we get - // it from the context. - private SharedPreferences mSharedPrefs; - - // Holds the page that we need to animate to, and the icon views that we need to animate up - // when we scroll to that page on resume. - private int mNewShortcutAnimatePage = -1; - private ArrayList mNewShortcutAnimateViews = new ArrayList(); - private ImageView mFolderIconImageView; - private Bitmap mFolderIconBitmap; - private Canvas mFolderIconCanvas; - private Rect mRectForFolderAnimation = new Rect(); - - private BubbleTextView mWaitingForResume; - - private Runnable mBuildLayersRunnable = new Runnable() { - public void run() { - if (mWorkspace != null) { - mWorkspace.buildPageHardwareLayers(); - } - } - }; - - private static ArrayList sPendingAddList - = new ArrayList(); - - private static class PendingAddArguments { - int requestCode; - Intent intent; - long container; - int screen; - int cellX; - int cellY; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - if (DEBUG_STRICT_MODE) { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectNetwork() // or .detectAll() for all detectable problems - .penaltyLog() - .build()); - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() - .detectLeakedSqlLiteObjects() - .detectLeakedClosableObjects() - .penaltyLog() - .penaltyDeath() - .build()); - } - - super.onCreate(savedInstanceState); - LauncherApplication app = ((LauncherApplication)getApplication()); - mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(), - Context.MODE_PRIVATE); - mModel = app.setLauncher(this); - mIconCache = app.getIconCache(); - mDragController = new DragController(this); - mInflater = getLayoutInflater(); - - mAppWidgetManager = AppWidgetManager.getInstance(this); - mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); - mAppWidgetHost.startListening(); - - if (PROFILE_STARTUP) { - android.os.Debug.startMethodTracing( - Environment.getExternalStorageDirectory() + "/launcher"); - } - - checkForLocaleChange(); - setContentView(R.layout.launcher); - setupViews(); - showFirstRunWorkspaceCling(); - - registerContentObservers(); - - lockAllApps(); - - mSavedState = savedInstanceState; - restoreState(mSavedState); - - // Update customization drawer _after_ restoring the states - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.onPackagesUpdated(); - } - - if (PROFILE_STARTUP) { - android.os.Debug.stopMethodTracing(); - } - - if (!mRestoring) { - mModel.startLoader(true); - } - - if (!mModel.isAllAppsLoaded()) { - ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent(); - mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent); - } - - // For handling default keys - mDefaultKeySsb = new SpannableStringBuilder(); - Selection.setSelection(mDefaultKeySsb, 0); - - IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - registerReceiver(mCloseSystemDialogsReceiver, filter); - - updateGlobalIcons(); - - // On large interfaces, we want the screen to auto-rotate based on the current orientation - unlockScreenOrientation(true); - } - - private void updateGlobalIcons() { - boolean searchVisible = false; - boolean voiceVisible = false; - // If we have a saved version of these external icons, we load them up immediately - int coi = getCurrentOrientationIndexForGlobalIcons(); - if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null || - sAppMarketIcon[coi] == null) { - updateAppMarketIcon(); - searchVisible = updateGlobalSearchIcon(); - voiceVisible = updateVoiceSearchIcon(searchVisible); - } - if (sGlobalSearchIcon[coi] != null) { - updateGlobalSearchIcon(sGlobalSearchIcon[coi]); - searchVisible = true; - } - if (sVoiceSearchIcon[coi] != null) { - updateVoiceSearchIcon(sVoiceSearchIcon[coi]); - voiceVisible = true; - } - if (sAppMarketIcon[coi] != null) { - updateAppMarketIcon(sAppMarketIcon[coi]); - } - mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); - } - - private void checkForLocaleChange() { - if (sLocaleConfiguration == null) { - new AsyncTask() { - @Override - protected LocaleConfiguration doInBackground(Void... unused) { - LocaleConfiguration localeConfiguration = new LocaleConfiguration(); - readConfiguration(Launcher.this, localeConfiguration); - return localeConfiguration; - } - - @Override - protected void onPostExecute(LocaleConfiguration result) { - sLocaleConfiguration = result; - checkForLocaleChange(); // recursive, but now with a locale configuration - } - }.execute(); - return; - } - - final Configuration configuration = getResources().getConfiguration(); - - final String previousLocale = sLocaleConfiguration.locale; - final String locale = configuration.locale.toString(); - - final int previousMcc = sLocaleConfiguration.mcc; - final int mcc = configuration.mcc; - - final int previousMnc = sLocaleConfiguration.mnc; - final int mnc = configuration.mnc; - - boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; - - if (localeChanged) { - sLocaleConfiguration.locale = locale; - sLocaleConfiguration.mcc = mcc; - sLocaleConfiguration.mnc = mnc; - - mIconCache.flush(); - - final LocaleConfiguration localeConfiguration = sLocaleConfiguration; - new Thread("WriteLocaleConfiguration") { - @Override - public void run() { - writeConfiguration(Launcher.this, localeConfiguration); - } - }.start(); - } - } - - private static class LocaleConfiguration { - public String locale; - public int mcc = -1; - public int mnc = -1; - } - - private static void readConfiguration(Context context, LocaleConfiguration configuration) { - DataInputStream in = null; - try { - in = new DataInputStream(context.openFileInput(PREFERENCES)); - configuration.locale = in.readUTF(); - configuration.mcc = in.readInt(); - configuration.mnc = in.readInt(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - // Ignore - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - private static void writeConfiguration(Context context, LocaleConfiguration configuration) { - DataOutputStream out = null; - try { - out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE)); - out.writeUTF(configuration.locale); - out.writeInt(configuration.mcc); - out.writeInt(configuration.mnc); - out.flush(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - //noinspection ResultOfMethodCallIgnored - context.getFileStreamPath(PREFERENCES).delete(); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - public DragLayer getDragLayer() { - return mDragLayer; - } - - boolean isDraggingEnabled() { - // We prevent dragging when we are loading the workspace as it is possible to pick up a view - // that is subsequently removed from the workspace in startBinding(). - return !mModel.isLoadingWorkspace(); - } - - static int getScreen() { - synchronized (sLock) { - return sScreen; - } - } - - static void setScreen(int screen) { - synchronized (sLock) { - sScreen = screen; - } - } - - /** - * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have - * a configuration step, this allows the proper animations to run after other transitions. - */ - private boolean completeAdd(PendingAddArguments args) { - boolean result = false; - switch (args.requestCode) { - case REQUEST_PICK_APPLICATION: - completeAddApplication(args.intent, args.container, args.screen, args.cellX, - args.cellY); - break; - case REQUEST_PICK_SHORTCUT: - processShortcut(args.intent); - break; - case REQUEST_CREATE_SHORTCUT: - completeAddShortcut(args.intent, args.container, args.screen, args.cellX, - args.cellY); - result = true; - break; - case REQUEST_CREATE_APPWIDGET: - int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); - completeAddAppWidget(appWidgetId, args.container, args.screen, null, null); - result = true; - break; - case REQUEST_PICK_WALLPAPER: - // We just wanted the activity result here so we can clear mWaitingForResult - break; - } - // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen, - // if you turned the screen off and then back while in All Apps, Launcher would not - // return to the workspace. Clearing mAddInfo.container here fixes this issue - resetAddInfo(); - return result; - } - - @Override - protected void onActivityResult( - final int requestCode, final int resultCode, final Intent data) { - if (requestCode == REQUEST_BIND_APPWIDGET) { - int appWidgetId = data != null ? - data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; - if (resultCode == RESULT_CANCELED) { - completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); - } else if (resultCode == RESULT_OK) { - addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo); - } - return; - } - boolean delayExitSpringLoadedMode = false; - boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || - requestCode == REQUEST_CREATE_APPWIDGET); - mWaitingForResult = false; - - // We have special handling for widgets - if (isWidgetDrop) { - int appWidgetId = data != null ? - data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; - if (appWidgetId < 0) { - Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" + - "widget configuration activity."); - completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); - } else { - completeTwoStageWidgetDrop(resultCode, appWidgetId); - } - return; - } - - // The pattern used here is that a user PICKs a specific application, - // which, depending on the target, might need to CREATE the actual target. - - // For example, the user would PICK_SHORTCUT for "Music playlist", and we - // launch over to the Music app to actually CREATE_SHORTCUT. - if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { - final PendingAddArguments args = new PendingAddArguments(); - args.requestCode = requestCode; - args.intent = data; - args.container = mPendingAddInfo.container; - args.screen = mPendingAddInfo.screen; - args.cellX = mPendingAddInfo.cellX; - args.cellY = mPendingAddInfo.cellY; - if (isWorkspaceLocked()) { - sPendingAddList.add(args); - } else { - delayExitSpringLoadedMode = completeAdd(args); - } - } - mDragLayer.clearAnimatedView(); - // Exit spring loaded mode if necessary after cancelling the configuration of a widget - exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode, - null); - } - - private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) { - CellLayout cellLayout = - (CellLayout) mWorkspace.getChildAt(mPendingAddInfo.screen); - Runnable onCompleteRunnable = null; - int animationType = 0; - - AppWidgetHostView boundWidget = null; - if (resultCode == RESULT_OK) { - animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; - final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, - mPendingAddWidgetInfo); - boundWidget = layout; - onCompleteRunnable = new Runnable() { - @Override - public void run() { - completeAddAppWidget(appWidgetId, mPendingAddInfo.container, - mPendingAddInfo.screen, layout, null); - exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false, - null); - } - }; - } else if (resultCode == RESULT_CANCELED) { - animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; - onCompleteRunnable = new Runnable() { - @Override - public void run() { - exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false, - null); - } - }; - } - if (mDragLayer.getAnimatedView() != null) { - mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout, - (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, - animationType, boundWidget, true); - } else { - // The animated view may be null in the case of a rotation during widget configuration - onCompleteRunnable.run(); - } - } - - @Override - protected void onResume() { - super.onResume(); - - // Process any items that were added while Launcher was away - InstallShortcutReceiver.flushInstallQueue(this); - - mPaused = false; - if (mRestoring || mOnResumeNeedsLoad) { - mWorkspaceLoading = true; - mModel.startLoader(true); - mRestoring = false; - mOnResumeNeedsLoad = false; - } - - // Reset the pressed state of icons that were locked in the press state while activities - // were launching - if (mWaitingForResume != null) { - // Resets the previous workspace icon press state - mWaitingForResume.setStayPressed(false); - } - if (mAppsCustomizeContent != null) { - // Resets the previous all apps icon press state - mAppsCustomizeContent.resetDrawableState(); - } - // It is possible that widgets can receive updates while launcher is not in the foreground. - // Consequently, the widgets will be inflated in the orientation of the foreground activity - // (framework issue). On resuming, we ensure that any widgets are inflated for the current - // orientation. - getWorkspace().reinflateWidgetsIfNecessary(); - - // Again, as with the above scenario, it's possible that one or more of the global icons - // were updated in the wrong orientation. - updateGlobalIcons(); - } - - @Override - protected void onPause() { - // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled - // to be consistent. So re-enable the flag here, and we will re-disable it as necessary - // when Launcher resumes and we are still in AllApps. - updateWallpaperVisibility(true); - - super.onPause(); - mPaused = true; - mDragController.cancelDrag(); - mDragController.resetLastGestureUpTime(); - } - - @Override - public Object onRetainNonConfigurationInstance() { - // Flag the loader to stop early before switching - mModel.stopLoader(); - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.surrender(); - } - return Boolean.TRUE; - } - - // We can't hide the IME if it was forced open. So don't bother - /* - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if (hasFocus) { - final InputMethodManager inputManager = (InputMethodManager) - getSystemService(Context.INPUT_METHOD_SERVICE); - WindowManager.LayoutParams lp = getWindow().getAttributes(); - inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new - android.os.Handler()) { - protected void onReceiveResult(int resultCode, Bundle resultData) { - Log.d(TAG, "ResultReceiver got resultCode=" + resultCode); - } - }); - Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged"); - } - } - */ - - private boolean acceptFilter() { - final InputMethodManager inputManager = (InputMethodManager) - getSystemService(Context.INPUT_METHOD_SERVICE); - return !inputManager.isFullscreenMode(); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - final int uniChar = event.getUnicodeChar(); - final boolean handled = super.onKeyDown(keyCode, event); - final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar); - if (!handled && acceptFilter() && isKeyNotWhitespace) { - boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, - keyCode, event); - if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { - // something usable has been typed - start a search - // the typed text will be retrieved and cleared by - // showSearchDialog() - // If there are multiple keystrokes before the search dialog takes focus, - // onSearchRequested() will be called for every keystroke, - // but it is idempotent, so it's fine. - return onSearchRequested(); - } - } - - // Eat the long press event so the keyboard doesn't come up. - if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) { - return true; - } - - return handled; - } - - private String getTypedText() { - return mDefaultKeySsb.toString(); - } - - private void clearTypedText() { - mDefaultKeySsb.clear(); - mDefaultKeySsb.clearSpans(); - Selection.setSelection(mDefaultKeySsb, 0); - } - - /** - * 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. - */ - private void restoreState(Bundle savedState) { - if (savedState == null) { - return; - } - - State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); - if (state == State.APPS_CUSTOMIZE) { - showAllApps(false); - } - - int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1); - if (currentScreen > -1) { - mWorkspace.setCurrentPage(currentScreen); - } - - final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1); - final int pendingAddScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); - - if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) { - mPendingAddInfo.container = pendingAddContainer; - mPendingAddInfo.screen = pendingAddScreen; - mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); - mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); - mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); - mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); - mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); - mWaitingForResult = true; - mRestoring = true; - } - - - boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); - if (renameFolder) { - long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); - mFolderInfo = mModel.getFolderById(this, sFolders, id); - mRestoring = true; - } - - - // Restore the AppsCustomize tab - if (mAppsCustomizeTabHost != null) { - String curTab = savedState.getString("apps_customize_currentTab"); - if (curTab != null) { - // We set this directly so that there is no delay before the tab is set - mAppsCustomizeContent.setContentType( - mAppsCustomizeTabHost.getContentTypeForTabTag(curTab)); - mAppsCustomizeTabHost.setCurrentTabByTag(curTab); - mAppsCustomizeContent.loadAssociatedPages( - mAppsCustomizeContent.getCurrentPage()); - } - - int currentIndex = savedState.getInt("apps_customize_currentIndex"); - mAppsCustomizeContent.restorePageForIndex(currentIndex); - } - } - - /** - * Finds all the views we need and configure them properly. - */ - private void setupViews() { - final DragController dragController = mDragController; - - mDragLayer = (DragLayer) findViewById(R.id.drag_layer); - mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); - mQsbDivider = (ImageView) findViewById(R.id.qsb_divider); - mDockDivider = (ImageView) findViewById(R.id.dock_divider); - - // Setup the drag layer - mDragLayer.setup(this, dragController); - - // Setup the hotseat - mHotseat = (Hotseat) findViewById(R.id.hotseat); - if (mHotseat != null) { - mHotseat.setup(this); - } - - // Setup the workspace - mWorkspace.setHapticFeedbackEnabled(false); - mWorkspace.setOnLongClickListener(this); - mWorkspace.setup(dragController); - dragController.addDragListener(mWorkspace); - - // Get the search/delete bar - mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar); - - // Setup AppsCustomize - mAppsCustomizeTabHost = (AppsCustomizeTabHost) - findViewById(R.id.apps_customize_pane); - mAppsCustomizeContent = (AppsCustomizePagedView) - mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); - mAppsCustomizeTabHost.setup(this); - mAppsCustomizeContent.setup(this, dragController); - - // Get the all apps button - mAllAppsButton = findViewById(R.id.all_apps_button); - if (mAllAppsButton != null) { - mAllAppsButton.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { - onTouchDownAllAppsButton(v); - } - return false; - } - }); - } - // Setup the drag controller (drop targets have to be added in reverse order in priority) - dragController.setDragScoller(mWorkspace); - dragController.setScrollView(mDragLayer); - dragController.setMoveTarget(mWorkspace); - dragController.addDropTarget(mWorkspace); - if (mSearchDropTargetBar != null) { - mSearchDropTargetBar.setup(this, dragController); - } - } - - /** - * Creates a view representing a shortcut. - * - * @param info The data structure describing the shortcut. - * - * @return A View inflated from R.layout.application. - */ - View createShortcut(ShortcutInfo info) { - return createShortcut(R.layout.application, - (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); - } - - /** - * Creates a view representing a shortcut inflated from the specified resource. - * - * @param layoutResId The id of the XML layout used to create the shortcut. - * @param parent The group the shortcut belongs to. - * @param info The data structure describing the shortcut. - * - * @return A View inflated from layoutResId. - */ - View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { - BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false); - favorite.applyFromShortcutInfo(info, mIconCache); - favorite.setOnClickListener(this); - return favorite; - } - - /** - * Add an application shortcut to the workspace. - * - * @param data The intent describing the application. - * @param cellInfo The position on screen where to create the shortcut. - */ - void completeAddApplication(Intent data, long container, int screen, int cellX, int cellY) { - final int[] cellXY = mTmpAddItemCellCoordinates; - final CellLayout layout = getCellLayout(container, screen); - - // First we check if we already know the exact location where we want to add this item. - if (cellX >= 0 && cellY >= 0) { - cellXY[0] = cellX; - cellXY[1] = cellY; - } else if (!layout.findCellForSpan(cellXY, 1, 1)) { - showOutOfSpaceMessage(isHotseatLayout(layout)); - return; - } - - final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this); - - if (info != null) { - info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - info.container = ItemInfo.NO_ID; - mWorkspace.addApplicationShortcut(info, layout, container, screen, cellXY[0], cellXY[1], - isWorkspaceLocked(), cellX, cellY); - } else { - Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data); - } - } - - /** - * Add a shortcut to the workspace. - * - * @param data The intent describing the shortcut. - * @param cellInfo The position on screen where to create the shortcut. - */ - private void completeAddShortcut(Intent data, long container, int screen, int cellX, - int cellY) { - int[] cellXY = mTmpAddItemCellCoordinates; - int[] touchXY = mPendingAddInfo.dropPos; - CellLayout layout = getCellLayout(container, screen); - - boolean foundCellSpan = false; - - ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null); - if (info == null) { - return; - } - final View view = createShortcut(info); - - // First we check if we already know the exact location where we want to add this item. - if (cellX >= 0 && cellY >= 0) { - cellXY[0] = cellX; - cellXY[1] = cellY; - foundCellSpan = true; - - // If appropriate, either create a folder or add to an existing folder - if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, - true, null,null)) { - return; - } - DragObject dragObject = new DragObject(); - dragObject.dragInfo = info; - if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, - true)) { - return; - } - } else if (touchXY != null) { - // when dragging and dropping, just find the closest free spot - int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY); - foundCellSpan = (result != null); - } else { - foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); - } - - if (!foundCellSpan) { - showOutOfSpaceMessage(isHotseatLayout(layout)); - return; - } - - LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false); - - if (!mRestoring) { - mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, - isWorkspaceLocked()); - } - } - - static int[] getSpanForWidget(Context context, ComponentName component, int minWidth, - int minHeight) { - Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null); - // We want to account for the extra amount of padding that we are adding to the widget - // to ensure that it gets the full amount of space that it has requested - int requiredWidth = minWidth + padding.left + padding.right; - int requiredHeight = minHeight + padding.top + padding.bottom; - return CellLayout.rectToCell(context.getResources(), requiredWidth, requiredHeight, null); - } - - static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) { - return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight); - } - - static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) { - return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight); - } - - static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) { - return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight); - } - - static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) { - return getSpanForWidget(context, info.componentName, info.minResizeWidth, - info.minResizeHeight); - } - - /** - * Add a widget to the workspace. - * - * @param appWidgetId The app widget id - * @param cellInfo The position on screen where to create the widget. - */ - private void completeAddAppWidget(final int appWidgetId, long container, int screen, - AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) { - if (appWidgetInfo == null) { - appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); - } - - // Calculate the grid spans needed to fit this widget - CellLayout layout = getCellLayout(container, screen); - - int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo); - int[] spanXY = getSpanForWidget(this, appWidgetInfo); - - // Try finding open space on Launcher screen - // 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 - int[] cellXY = mTmpAddItemCellCoordinates; - int[] touchXY = mPendingAddInfo.dropPos; - int[] finalSpan = new int[2]; - boolean foundCellSpan = false; - if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) { - cellXY[0] = mPendingAddInfo.cellX; - cellXY[1] = mPendingAddInfo.cellY; - spanXY[0] = mPendingAddInfo.spanX; - spanXY[1] = mPendingAddInfo.spanY; - foundCellSpan = true; - } else if (touchXY != null) { - // when dragging and dropping, just find the closest free spot - int[] result = layout.findNearestVacantArea( - touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0], - spanXY[1], cellXY, finalSpan); - spanXY[0] = finalSpan[0]; - spanXY[1] = finalSpan[1]; - foundCellSpan = (result != null); - } else { - foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]); - } - - if (!foundCellSpan) { - if (appWidgetId != -1) { - // Deleting an app widget ID is a void call but writes to disk before returning - // to the caller... - new Thread("deleteAppWidgetId") { - public void run() { - mAppWidgetHost.deleteAppWidgetId(appWidgetId); - } - }.start(); - } - showOutOfSpaceMessage(isHotseatLayout(layout)); - return; - } - - // Build Launcher-specific widget info and save to database - LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId, - appWidgetInfo.provider); - launcherInfo.spanX = spanXY[0]; - launcherInfo.spanY = spanXY[1]; - launcherInfo.minSpanX = mPendingAddInfo.minSpanX; - launcherInfo.minSpanY = mPendingAddInfo.minSpanY; - - LauncherModel.addItemToDatabase(this, launcherInfo, - container, screen, cellXY[0], cellXY[1], false); - - if (!mRestoring) { - if (hostView == null) { - // Perform actual inflation because we're live - launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); - launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); - } else { - // The AppWidgetHostView has already been inflated and instantiated - launcherInfo.hostView = hostView; - } - - launcherInfo.hostView.setTag(launcherInfo); - launcherInfo.hostView.setVisibility(View.VISIBLE); - launcherInfo.notifyWidgetSizeChanged(this); - mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1], - launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); - - addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo); - } - resetAddInfo(); - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mUserPresent = false; - mDragLayer.clearAllResizeFrames(); - updateRunning(); - - // Reset AllApps to its initial state only if we are not in the middle of - // processing a multi-step drop - if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) { - mAppsCustomizeTabHost.reset(); - showWorkspace(false); - } - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateRunning(); - } - } - }; - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - - // Listen for broadcasts related to user-presence - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_USER_PRESENT); - registerReceiver(mReceiver, filter); - - mAttached = true; - mVisible = true; - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mVisible = false; - - if (mAttached) { - unregisterReceiver(mReceiver); - mAttached = false; - } - updateRunning(); - } - - public void onWindowVisibilityChanged(int visibility) { - mVisible = visibility == View.VISIBLE; - updateRunning(); - // The following code used to be in onResume, but it turns out onResume is called when - // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged - // is a more appropriate event to handle - if (mVisible) { - mAppsCustomizeTabHost.onWindowVisible(); - if (!mWorkspaceLoading) { - final ViewTreeObserver observer = mWorkspace.getViewTreeObserver(); - // We want to let Launcher draw itself at least once before we force it to build - // layers on all the workspace pages, so that transitioning to Launcher from other - // apps is nice and speedy. Usually the first call to preDraw doesn't correspond to - // a true draw so we wait until the second preDraw call to be safe - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - // We delay the layer building a bit in order to give - // other message processing a time to run. In particular - // this avoids a delay in hiding the IME if it was - // currently shown, because doing that may involve - // some communication back with the app. - mWorkspace.postDelayed(mBuildLayersRunnable, 500); - - observer.removeOnPreDrawListener(this); - return true; - } - }); - } - // When Launcher comes back to foreground, a different Activity might be responsible for - // the app market intent, so refresh the icon - updateAppMarketIcon(); - clearTypedText(); - } - } - - private void sendAdvanceMessage(long delay) { - mHandler.removeMessages(ADVANCE_MSG); - Message msg = mHandler.obtainMessage(ADVANCE_MSG); - mHandler.sendMessageDelayed(msg, delay); - mAutoAdvanceSentTime = System.currentTimeMillis(); - } - - private void updateRunning() { - boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty(); - if (autoAdvanceRunning != mAutoAdvanceRunning) { - mAutoAdvanceRunning = autoAdvanceRunning; - if (autoAdvanceRunning) { - long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft; - sendAdvanceMessage(delay); - } else { - if (!mWidgetsToAdvance.isEmpty()) { - mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - - (System.currentTimeMillis() - mAutoAdvanceSentTime)); - } - mHandler.removeMessages(ADVANCE_MSG); - mHandler.removeMessages(0); // Remove messages sent using postDelayed() - } - } - } - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == ADVANCE_MSG) { - int i = 0; - for (View key: mWidgetsToAdvance.keySet()) { - final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); - final int delay = mAdvanceStagger * i; - if (v instanceof Advanceable) { - postDelayed(new Runnable() { - public void run() { - ((Advanceable) v).advance(); - } - }, delay); - } - i++; - } - sendAdvanceMessage(mAdvanceInterval); - } - } - }; - - void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) { - if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return; - View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId); - if (v instanceof Advanceable) { - mWidgetsToAdvance.put(hostView, appWidgetInfo); - ((Advanceable) v).fyiWillBeAdvancedByHostKThx(); - updateRunning(); - } - } - - void removeWidgetToAutoAdvance(View hostView) { - if (mWidgetsToAdvance.containsKey(hostView)) { - mWidgetsToAdvance.remove(hostView); - updateRunning(); - } - } - - public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) { - removeWidgetToAutoAdvance(launcherInfo.hostView); - launcherInfo.hostView = null; - } - - void showOutOfSpaceMessage(boolean isHotseatLayout) { - int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space); - Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show(); - } - - public LauncherAppWidgetHost getAppWidgetHost() { - return mAppWidgetHost; - } - - public LauncherModel getModel() { - return mModel; - } - - void closeSystemDialogs() { - getWindow().closeAllPanels(); - - // Whatever we were doing is hereby canceled. - mWaitingForResult = false; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - // Close the menu - if (Intent.ACTION_MAIN.equals(intent.getAction())) { - // also will cancel mWaitingForResult. - closeSystemDialogs(); - - boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) - != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - - Folder openFolder = mWorkspace.getOpenFolder(); - // In all these cases, only animate if we're already on home - mWorkspace.exitWidgetResizeMode(); - if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() && - openFolder == null) { - mWorkspace.moveToDefaultScreen(true); - } - - closeFolder(); - exitSpringLoadedDragMode(); - showWorkspace(alreadyOnHome); - - final View v = getWindow().peekDecorView(); - if (v != null && v.getWindowToken() != null) { - InputMethodManager imm = (InputMethodManager)getSystemService( - INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - - // Reset AllApps to its initial state - if (!alreadyOnHome && mAppsCustomizeTabHost != null) { - mAppsCustomizeTabHost.reset(); - } - } - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - // Do not call super here - mSavedInstanceState = savedInstanceState; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage()); - super.onSaveInstanceState(outState); - - outState.putInt(RUNTIME_STATE, mState.ordinal()); - // We close any open folder since it will not be re-opened, and we need to make sure - // this state is reflected. - closeFolder(); - - if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screen > -1 && - mWaitingForResult) { - outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screen); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY); - outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo); - } - - if (mFolderInfo != null && mWaitingForResult) { - outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); - outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); - } - - // Save the current AppsCustomize tab - if (mAppsCustomizeTabHost != null) { - String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag(); - if (currentTabTag != null) { - outState.putString("apps_customize_currentTab", currentTabTag); - } - int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex(); - outState.putInt("apps_customize_currentIndex", currentIndex); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - - // Remove all pending runnables - mHandler.removeMessages(ADVANCE_MSG); - mHandler.removeMessages(0); - mWorkspace.removeCallbacks(mBuildLayersRunnable); - - // Stop callbacks from LauncherModel - LauncherApplication app = ((LauncherApplication) getApplication()); - mModel.stopLoader(); - app.setLauncher(null); - - try { - mAppWidgetHost.stopListening(); - } catch (NullPointerException ex) { - Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); - } - mAppWidgetHost = null; - - mWidgetsToAdvance.clear(); - - TextKeyListener.getInstance().release(); - - - unbindWorkspaceAndHotseatItems(); - - getContentResolver().unregisterContentObserver(mWidgetObserver); - unregisterReceiver(mCloseSystemDialogsReceiver); - - mDragLayer.clearAllResizeFrames(); - ((ViewGroup) mWorkspace.getParent()).removeAllViews(); - mWorkspace.removeAllViews(); - mWorkspace = null; - mDragController = null; - - ValueAnimator.clearAllAnimations(); - } - - public DragController getDragController() { - return mDragController; - } - - @Override - public void startActivityForResult(Intent intent, int requestCode) { - if (requestCode >= 0) mWaitingForResult = true; - super.startActivityForResult(intent, requestCode); - } - - /** - * Indicates that we want global search for this activity by setting the globalSearch - * argument for {@link #startSearch} to true. - */ - @Override - public void startSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData, boolean globalSearch) { - - showWorkspace(true); - - if (initialQuery == null) { - // Use any text typed in the launcher as the initial query - initialQuery = getTypedText(); - } - if (appSearchData == null) { - appSearchData = new Bundle(); - appSearchData.putString(Search.SOURCE, "launcher-search"); - } - Rect sourceBounds = mSearchDropTargetBar.getSearchBarBounds(); - - final SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), - appSearchData, globalSearch, sourceBounds); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (isWorkspaceLocked()) { - return false; - } - - super.onCreateOptionsMenu(menu); - - Intent manageApps = new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS); - manageApps.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS); - settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - String helpUrl = getString(R.string.help_url); - Intent help = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl)); - help.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - - menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper) - .setIcon(android.R.drawable.ic_menu_gallery) - .setAlphabeticShortcut('W'); - menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps) - .setIcon(android.R.drawable.ic_menu_manage) - .setIntent(manageApps) - .setAlphabeticShortcut('M'); - menu.add(0, MENU_SYSTEM_SETTINGS, 0, R.string.menu_settings) - .setIcon(android.R.drawable.ic_menu_preferences) - .setIntent(settings) - .setAlphabeticShortcut('P'); - if (!helpUrl.isEmpty()) { - menu.add(0, MENU_HELP, 0, R.string.menu_help) - .setIcon(android.R.drawable.ic_menu_help) - .setIntent(help) - .setAlphabeticShortcut('H'); - } - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - if (mAppsCustomizeTabHost.isTransitioning()) { - return false; - } - boolean allAppsVisible = (mAppsCustomizeTabHost.getVisibility() == View.VISIBLE); - menu.setGroupVisible(MENU_GROUP_WALLPAPER, !allAppsVisible); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case MENU_WALLPAPER_SETTINGS: - startWallpaper(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onSearchRequested() { - startSearch(null, false, null, true); - // Use a custom animation for launching search - overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast); - return true; - } - - public boolean isWorkspaceLocked() { - return mWorkspaceLoading || mWaitingForResult; - } - - private void resetAddInfo() { - mPendingAddInfo.container = ItemInfo.NO_ID; - mPendingAddInfo.screen = -1; - mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1; - mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1; - mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1; - mPendingAddInfo.dropPos = null; - } - - void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, - AppWidgetProviderInfo appWidgetInfo) { - if (appWidgetInfo.configure != null) { - mPendingAddWidgetInfo = appWidgetInfo; - - // Launch over to configure widget, if needed - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); - intent.setComponent(appWidgetInfo.configure); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); - } else { - // Otherwise just add it - completeAddAppWidget(appWidgetId, info.container, info.screen, boundWidget, - appWidgetInfo); - // Exit spring loaded mode if necessary after adding the widget - exitSpringLoadedDragModeDelayed(true, false, null); - } - } - - /** - * Process a shortcut drop. - * - * @param componentName The name of the component - * @param screen The screen where it should be added - * @param cell The cell it should be added to, optional - * @param position The location on the screen where it was dropped, optional - */ - void processShortcutFromDrop(ComponentName componentName, long container, int screen, - int[] cell, int[] loc) { - resetAddInfo(); - mPendingAddInfo.container = container; - mPendingAddInfo.screen = screen; - mPendingAddInfo.dropPos = loc; - - if (cell != null) { - mPendingAddInfo.cellX = cell[0]; - mPendingAddInfo.cellY = cell[1]; - } - - Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); - createShortcutIntent.setComponent(componentName); - processShortcut(createShortcutIntent); - } - - /** - * Process a widget drop. - * - * @param info The PendingAppWidgetInfo of the widget being added. - * @param screen The screen where it should be added - * @param cell The cell it should be added to, optional - * @param position The location on the screen where it was dropped, optional - */ - void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen, - int[] cell, int[] span, int[] loc) { - resetAddInfo(); - mPendingAddInfo.container = info.container = container; - mPendingAddInfo.screen = info.screen = screen; - mPendingAddInfo.dropPos = loc; - mPendingAddInfo.minSpanX = info.minSpanX; - mPendingAddInfo.minSpanY = info.minSpanY; - - if (cell != null) { - mPendingAddInfo.cellX = cell[0]; - mPendingAddInfo.cellY = cell[1]; - } - if (span != null) { - mPendingAddInfo.spanX = span[0]; - mPendingAddInfo.spanY = span[1]; - } - - AppWidgetHostView hostView = info.boundWidget; - int appWidgetId; - if (hostView != null) { - appWidgetId = hostView.getAppWidgetId(); - addAppWidgetImpl(appWidgetId, info, hostView, info.info); - } else { - // In this case, we either need to start an activity to get permission to bind - // the widget, or we need to start an activity to configure the widget, or both. - appWidgetId = getAppWidgetHost().allocateAppWidgetId(); - if (mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.componentName)) { - addAppWidgetImpl(appWidgetId, info, null, info.info); - } else { - mPendingAddWidgetInfo = info.info; - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName); - startActivityForResult(intent, REQUEST_BIND_APPWIDGET); - } - } - } - - void processShortcut(Intent intent) { - // Handle case where user selected "Applications" - String applicationName = getResources().getString(R.string.group_applications); - String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - - if (applicationName != null && applicationName.equals(shortcutName)) { - Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); - pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); - pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application)); - startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION); - } else { - startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT); - } - } - - void processWallpaper(Intent intent) { - startActivityForResult(intent, REQUEST_PICK_WALLPAPER); - } - - FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX, - int cellY) { - final FolderInfo folderInfo = new FolderInfo(); - folderInfo.title = getText(R.string.folder_name); - - // Update the model - LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY, - false); - sFolders.put(folderInfo.id, folderInfo); - - // Create the view - FolderIcon newFolder = - FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache); - mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1, - isWorkspaceLocked()); - return newFolder; - } - - void removeFolder(FolderInfo folder) { - sFolders.remove(folder.id); - } - - private void startWallpaper() { - showWorkspace(true); - final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); - Intent chooser = Intent.createChooser(pickWallpaper, - getText(R.string.chooser_wallpaper)); - // NOTE: Adds a configure option to the chooser if the wallpaper supports it - // Removed in Eclair MR1 -// WallpaperManager wm = (WallpaperManager) -// getSystemService(Context.WALLPAPER_SERVICE); -// WallpaperInfo wi = wm.getWallpaperInfo(); -// if (wi != null && wi.getSettingsActivity() != null) { -// LabeledIntent li = new LabeledIntent(getPackageName(), -// R.string.configure_wallpaper, 0); -// li.setClassName(wi.getPackageName(), wi.getSettingsActivity()); -// chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li }); -// } - startActivityForResult(chooser, REQUEST_PICK_WALLPAPER); - } - - /** - * Registers various content observers. The current implementation registers - * only a favorites observer to keep track of the favorites applications. - */ - private void registerContentObservers() { - ContentResolver resolver = getContentResolver(); - resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, - true, mWidgetObserver); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_HOME: - return true; - case KeyEvent.KEYCODE_VOLUME_DOWN: - if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) { - dumpState(); - return true; - } - break; - } - } else if (event.getAction() == KeyEvent.ACTION_UP) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_HOME: - return true; - } - } - - return super.dispatchKeyEvent(event); - } - - @Override - public void onBackPressed() { - if (mState == State.APPS_CUSTOMIZE) { - showWorkspace(true); - } else if (mWorkspace.getOpenFolder() != null) { - Folder openFolder = mWorkspace.getOpenFolder(); - if (openFolder.isEditingName()) { - openFolder.dismissEditingName(); - } else { - closeFolder(); - } - } else { - mWorkspace.exitWidgetResizeMode(); - - // Back button is a no-op here, but give at least some feedback for the button press - mWorkspace.showOutlinesTemporarily(); - } - } - - /** - * Re-listen when widgets are reset. - */ - private void onAppWidgetReset() { - if (mAppWidgetHost != null) { - mAppWidgetHost.startListening(); - } - } - - /** - * Go through the and disconnect any of the callbacks in the drawables and the views or we - * leak the previous Home screen on orientation change. - */ - private void unbindWorkspaceAndHotseatItems() { - if (mModel != null) { - mModel.unbindWorkspaceItems(); - } - } - - /** - * Launches the intent referred by the clicked shortcut. - * - * @param v The view representing the clicked shortcut. - */ - public void onClick(View v) { - // Make sure that rogue clicks don't get through while allapps is launching, or after the - // view has detached (it's possible for this to happen if the view is removed mid touch). - if (v.getWindowToken() == null) { - return; - } - - if (!mWorkspace.isFinishedSwitchingState()) { - return; - } - - Object tag = v.getTag(); - if (tag instanceof ShortcutInfo) { - // Open shortcut - final Intent intent = ((ShortcutInfo) tag).intent; - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - intent.setSourceBounds(new Rect(pos[0], pos[1], - pos[0] + v.getWidth(), pos[1] + v.getHeight())); - - boolean success = startActivitySafely(v, intent, tag); - - if (success && v instanceof BubbleTextView) { - mWaitingForResume = (BubbleTextView) v; - mWaitingForResume.setStayPressed(true); - } - } else if (tag instanceof FolderInfo) { - if (v instanceof FolderIcon) { - FolderIcon fi = (FolderIcon) v; - handleFolderClick(fi); - } - } else if (v == mAllAppsButton) { - if (mState == State.APPS_CUSTOMIZE) { - showWorkspace(true); - } else { - onClickAllAppsButton(v); - } - } - } - - 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) { - v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - - onSearchRequested(); - } - - /** - * Event handler for the voice button - * - * @param v The view that was clicked. - */ - public void onClickVoiceButton(View v) { - v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - - try { - final SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - ComponentName activityName = searchManager.getGlobalSearchActivity(); - Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (activityName != null) { - intent.setPackage(activityName.getPackageName()); - } - startActivity(null, intent, "onClickVoiceButton"); - overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast); - } catch (ActivityNotFoundException e) { - Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivitySafely(null, intent, "onClickVoiceButton"); - } - } - - /** - * 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 onTouchDownAllAppsButton(View v) { - // Provide the same haptic feedback that the system offers for virtual keys. - v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - } - - public void onClickAppMarketButton(View v) { - if (mAppMarketIntent != null) { - startActivitySafely(v, mAppMarketIntent, "app market"); - } else { - Log.e(TAG, "Invalid app market intent."); - } - } - - void startApplicationDetailsActivity(ComponentName componentName) { - String packageName = componentName.getPackageName(); - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.fromParts("package", packageName, null)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - startActivitySafely(null, intent, "startApplicationDetailsActivity"); - } - - 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)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - startActivity(intent); - } - } - - boolean startActivity(View v, Intent intent, Object tag) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - try { - // Only launch using the new animation if the shortcut has not opted out (this is a - // private contract between launcher and may be ignored in the future). - boolean useLaunchAnimation = (v != null) && - !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); - if (useLaunchAnimation) { - ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0, - v.getMeasuredWidth(), v.getMeasuredHeight()); - - startActivity(intent, opts.toBundle()); - } else { - startActivity(intent); - } - return true; - } catch (SecurityException e) { - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Launcher does not have the permission to launch " + intent + - ". Make sure to create a MAIN intent-filter for the corresponding activity " + - "or use the exported attribute for this activity. " - + "tag="+ tag + " intent=" + intent, e); - } - return false; - } - - boolean startActivitySafely(View v, Intent intent, Object tag) { - boolean success = false; - try { - success = startActivity(v, intent, tag); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); - } - return success; - } - - void startActivityForResultSafely(Intent intent, int requestCode) { - try { - startActivityForResult(intent, requestCode); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - } catch (SecurityException e) { - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Launcher does not have the permission to launch " + intent + - ". Make sure to create a MAIN intent-filter for the corresponding activity " + - "or use the exported attribute for this activity.", e); - } - } - - private void handleFolderClick(FolderIcon folderIcon) { - final FolderInfo info = folderIcon.mInfo; - Folder openFolder = mWorkspace.getFolderForTag(info); - - // If the folder info reports that the associated folder is open, then verify that - // it is actually opened. There have been a few instances where this gets out of sync. - if (info.opened && openFolder == null) { - Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: " - + info.screen + " (" + info.cellX + ", " + info.cellY + ")"); - info.opened = false; - } - - if (!info.opened) { - // Close any open folder - closeFolder(); - // Open the requested folder - openFolder(folderIcon); - } else { - // Find the open folder... - int folderScreen; - if (openFolder != null) { - folderScreen = mWorkspace.getPageForView(openFolder); - // .. and close it - closeFolder(openFolder); - if (folderScreen != mWorkspace.getCurrentPage()) { - // Close any folder open on the current screen - closeFolder(); - // Pull the folder onto this screen - openFolder(folderIcon); - } - } - } - } - - /** - * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView - * in the DragLayer in the exact absolute location of the original FolderIcon. - */ - private void copyFolderIconToImage(FolderIcon fi) { - final int width = fi.getMeasuredWidth(); - final int height = fi.getMeasuredHeight(); - - // Lazy load ImageView, Bitmap and Canvas - if (mFolderIconImageView == null) { - mFolderIconImageView = new ImageView(this); - } - if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width || - mFolderIconBitmap.getHeight() != height) { - mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mFolderIconCanvas = new Canvas(mFolderIconBitmap); - } - - DragLayer.LayoutParams lp; - if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) { - lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams(); - } else { - lp = new DragLayer.LayoutParams(width, height); - } - - mDragLayer.getViewRectRelativeToSelf(fi, mRectForFolderAnimation); - lp.customPosition = true; - lp.x = mRectForFolderAnimation.left; - lp.y = mRectForFolderAnimation.top; - lp.width = width; - lp.height = height; - - mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR); - fi.draw(mFolderIconCanvas); - mFolderIconImageView.setImageBitmap(mFolderIconBitmap); - if (fi.mFolder != null) { - mFolderIconImageView.setPivotX(fi.mFolder.getPivotXForIconAnimation()); - mFolderIconImageView.setPivotY(fi.mFolder.getPivotYForIconAnimation()); - } - // Just in case this image view is still in the drag layer from a previous animation, - // we remove it and re-add it. - if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) { - mDragLayer.removeView(mFolderIconImageView); - } - mDragLayer.addView(mFolderIconImageView, lp); - if (fi.mFolder != null) { - fi.mFolder.bringToFront(); - } - } - - private void growAndFadeOutFolderIcon(FolderIcon fi) { - if (fi == null) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f); - - FolderInfo info = (FolderInfo) fi.getTag(); - if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - CellLayout cl = (CellLayout) fi.getParent().getParent(); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams(); - cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); - } - - // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original - copyFolderIconToImage(fi); - fi.setVisibility(View.INVISIBLE); - - ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(mFolderIconImageView, alpha, - scaleX, scaleY); - oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration)); - oa.start(); - } - - private void shrinkAndFadeInFolderIcon(final FolderIcon fi) { - if (fi == null) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); - - final CellLayout cl = (CellLayout) fi.getParent().getParent(); - - // We remove and re-draw the FolderIcon in-case it has changed - mDragLayer.removeView(mFolderIconImageView); - copyFolderIconToImage(fi); - ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(mFolderIconImageView, alpha, - scaleX, scaleY); - oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration)); - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (cl != null) { - cl.clearFolderLeaveBehind(); - // Remove the ImageView copy of the FolderIcon and make the original visible. - mDragLayer.removeView(mFolderIconImageView); - fi.setVisibility(View.VISIBLE); - } - } - }); - oa.start(); - } - - /** - * 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. - */ - public void openFolder(FolderIcon folderIcon) { - Folder folder = folderIcon.mFolder; - FolderInfo info = folder.mInfo; - - info.opened = true; - - // Just verify that the folder hasn't already been added to the DragLayer. - // There was a one-off crash where the folder had a parent already. - if (folder.getParent() == null) { - mDragLayer.addView(folder); - mDragController.addDropTarget((DropTarget) folder); - } else { - Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" + - folder.getParent() + ")."); - } - folder.animateOpen(); - growAndFadeOutFolderIcon(folderIcon); - } - - public void closeFolder() { - Folder folder = mWorkspace.getOpenFolder(); - if (folder != null) { - if (folder.isEditingName()) { - folder.dismissEditingName(); - } - closeFolder(folder); - - // Dismiss the folder cling - dismissFolderCling(null); - } - } - - void closeFolder(Folder folder) { - folder.getInfo().opened = false; - - ViewGroup parent = (ViewGroup) folder.getParent().getParent(); - if (parent != null) { - FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo); - shrinkAndFadeInFolderIcon(fi); - } - folder.animateClosed(); - } - - public boolean onLongClick(View v) { - if (!isDraggingEnabled()) return false; - if (isWorkspaceLocked()) return false; - if (mState != State.WORKSPACE) return false; - - if (!(v instanceof CellLayout)) { - v = (View) v.getParent().getParent(); - } - - resetAddInfo(); - CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); - // This happens when long clicking an item with the dpad/trackball - if (longClickCellInfo == null) { - return true; - } - - // The hotseat touch handling does not go through Workspace, and we always allow long press - // on hotseat items. - final View itemUnderLongClick = longClickCellInfo.cell; - boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress(); - if (allowLongPress && !mDragController.isDragging()) { - if (itemUnderLongClick == null) { - // User long pressed on empty space - mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - startWallpaper(); - } else { - if (!(itemUnderLongClick instanceof Folder)) { - // User long pressed on an item - mWorkspace.startDrag(longClickCellInfo); - } - } - } - return true; - } - - boolean isHotseatLayout(View layout) { - return mHotseat != null && layout != null && - (layout instanceof CellLayout) && (layout == mHotseat.getLayout()); - } - Hotseat getHotseat() { - return mHotseat; - } - SearchDropTargetBar getSearchBar() { - return mSearchDropTargetBar; - } - - /** - * Returns the CellLayout of the specified container at the specified screen. - */ - CellLayout getCellLayout(long container, int screen) { - if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - if (mHotseat != null) { - return mHotseat.getLayout(); - } else { - return null; - } - } else { - return (CellLayout) mWorkspace.getChildAt(screen); - } - } - - Workspace getWorkspace() { - return mWorkspace; - } - - // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. - public boolean isAllAppsVisible() { - return (mState == State.APPS_CUSTOMIZE); - } - - public boolean isAllAppsButtonRank(int rank) { - return mHotseat.isAllAppsButtonRank(rank); - } - - // AllAppsView.Watcher - public void zoomed(float zoom) { - if (zoom == 1.0f) { - mWorkspace.setVisibility(View.GONE); - } - } - - /** - * 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 (eg. APPS_CUSTOMIZE) - * @param scaleFactor The scale factor used for the zoom - */ - private void setPivotsForZoom(View view, float scaleFactor) { - view.setPivotX(view.getWidth() / 2.0f); - view.setPivotY(view.getHeight() / 2.0f); - } - - void disableWallpaperIfInAllApps() { - // Only disable it if we are in all apps - if (mState == State.APPS_CUSTOMIZE) { - if (mAppsCustomizeTabHost != null && - !mAppsCustomizeTabHost.isTransitioning()) { - updateWallpaperVisibility(false); - } - } - } - - void updateWallpaperVisibility(boolean visible) { - int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; - int curflags = getWindow().getAttributes().flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; - if (wpflags != curflags) { - getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); - } - } - - private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { - if (v instanceof LauncherTransitionable) { - ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace); - } - } - - private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { - if (v instanceof LauncherTransitionable) { - ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace); - } - - // Update the workspace transition step as well - dispatchOnLauncherTransitionStep(v, 0f); - } - - private void dispatchOnLauncherTransitionStep(View v, float t) { - if (v instanceof LauncherTransitionable) { - ((LauncherTransitionable) v).onLauncherTransitionStep(this, t); - } - } - - private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { - if (v instanceof LauncherTransitionable) { - ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace); - } - - // Update the workspace transition step as well - dispatchOnLauncherTransitionStep(v, 1f); - } - - /** - * Things to test when changing the following seven functions. - * - Home from workspace - * - from center screen - * - from other screens - * - Home from all apps - * - from center screen - * - from other screens - * - Back from all apps - * - from center screen - * - from other screens - * - Launch app from workspace and quit - * - with back - * - with home - * - Launch app from all apps and quit - * - with back - * - with home - * - Go to a screen that's not the default, then all - * apps, and launch and app, and go back - * - with back - * -with home - * - On workspace, long press power and go back - * - with back - * - with home - * - On all apps, long press power and go back - * - with back - * - with home - * - On workspace, power off - * - On all apps, power off - * - Launch an app and turn off the screen while in that app - * - Go back with home key - * - Go back with back key TODO: make this not go to workspace - * - From all apps - * - From workspace - * - Enter and exit car mode (becuase it causes an extra configuration changed) - * - From all apps - * - From the center workspace - * - From another workspace - */ - - /** - * 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. - */ - private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) { - if (mStateAnimation != null) { - mStateAnimation.cancel(); - mStateAnimation = null; - } - final Resources res = getResources(); - - final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime); - final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime); - final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); - final View fromView = mWorkspace; - final AppsCustomizeTabHost toView = mAppsCustomizeTabHost; - final int startDelay = - res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger); - - setPivotsForZoom(toView, scale); - - // Shrink workspaces away if going to AppsCustomize from workspace - Animator workspaceAnim = - mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated); - - if (animated) { - toView.setScaleX(scale); - toView.setScaleY(scale); - final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView); - scaleAnim. - scaleX(1f).scaleY(1f). - setDuration(duration). - setInterpolator(new Workspace.ZoomOutInterpolator()); - - toView.setVisibility(View.VISIBLE); - toView.setAlpha(0f); - final ObjectAnimator alphaAnim = ObjectAnimator - .ofFloat(toView, "alpha", 0f, 1f) - .setDuration(fadeDuration); - alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f)); - alphaAnim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - dispatchOnLauncherTransitionStep(fromView, t); - dispatchOnLauncherTransitionStep(toView, t); - } - }); - - // toView should appear right at the end of the workspace shrink - // animation - mStateAnimation = new AnimatorSet(); - mStateAnimation.play(scaleAnim).after(startDelay); - mStateAnimation.play(alphaAnim).after(startDelay); - - mStateAnimation.addListener(new AnimatorListenerAdapter() { - boolean animationCancelled = false; - - @Override - public void onAnimationStart(Animator animation) { - updateWallpaperVisibility(true); - // Prepare the position - toView.setTranslationX(0.0f); - toView.setTranslationY(0.0f); - toView.setVisibility(View.VISIBLE); - toView.bringToFront(); - } - @Override - public void onAnimationEnd(Animator animation) { - dispatchOnLauncherTransitionEnd(fromView, animated, false); - dispatchOnLauncherTransitionEnd(toView, animated, false); - - if (!springLoaded && !LauncherApplication.isScreenLarge()) { - // Hide the workspace scrollbar - mWorkspace.hideScrollingIndicator(true); - hideDockDivider(); - } - if (!animationCancelled) { - updateWallpaperVisibility(false); - } - - // Hide the search bar - mSearchDropTargetBar.hideSearchBar(false); - } - - @Override - public void onAnimationCancel(Animator animation) { - animationCancelled = true; - } - }); - - if (workspaceAnim != null) { - mStateAnimation.play(workspaceAnim); - } - - boolean delayAnim = false; - final ViewTreeObserver observer; - - dispatchOnLauncherTransitionPrepare(fromView, animated, false); - dispatchOnLauncherTransitionPrepare(toView, animated, false); - - // If any of the objects being animated haven't been measured/laid out - // yet, delay the animation until we get a layout pass - if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) || - (mWorkspace.getMeasuredWidth() == 0) || - (toView.getMeasuredWidth() == 0)) { - observer = mWorkspace.getViewTreeObserver(); - delayAnim = true; - } else { - observer = null; - } - - final AnimatorSet stateAnimation = mStateAnimation; - final Runnable startAnimRunnable = new Runnable() { - public void run() { - // Check that mStateAnimation hasn't changed while - // we waited for a layout/draw pass - if (mStateAnimation != stateAnimation) - return; - setPivotsForZoom(toView, scale); - dispatchOnLauncherTransitionStart(fromView, animated, false); - dispatchOnLauncherTransitionStart(toView, animated, false); - toView.post(new Runnable() { - public void run() { - // Check that mStateAnimation hasn't changed while - // we waited for a layout/draw pass - if (mStateAnimation != stateAnimation) - return; - mStateAnimation.start(); - } - }); - } - }; - if (delayAnim) { - final OnGlobalLayoutListener delayedStart = new OnGlobalLayoutListener() { - public void onGlobalLayout() { - toView.post(startAnimRunnable); - observer.removeOnGlobalLayoutListener(this); - } - }; - observer.addOnGlobalLayoutListener(delayedStart); - } else { - startAnimRunnable.run(); - } - } else { - toView.setTranslationX(0.0f); - toView.setTranslationY(0.0f); - toView.setScaleX(1.0f); - toView.setScaleY(1.0f); - toView.setVisibility(View.VISIBLE); - toView.bringToFront(); - - if (!springLoaded && !LauncherApplication.isScreenLarge()) { - // Hide the workspace scrollbar - mWorkspace.hideScrollingIndicator(true); - hideDockDivider(); - - // Hide the search bar - mSearchDropTargetBar.hideSearchBar(false); - } - dispatchOnLauncherTransitionPrepare(fromView, animated, false); - dispatchOnLauncherTransitionStart(fromView, animated, false); - dispatchOnLauncherTransitionEnd(fromView, animated, false); - dispatchOnLauncherTransitionPrepare(toView, animated, false); - dispatchOnLauncherTransitionStart(toView, animated, false); - dispatchOnLauncherTransitionEnd(toView, animated, false); - updateWallpaperVisibility(false); - } - } - - /** - * Zoom the camera back into the workspace, hiding 'fromView'. - * This is the opposite of showAppsCustomizeHelper. - * @param animated If true, the transition will be animated. - */ - private void hideAppsCustomizeHelper(State toState, final boolean animated, - final boolean springLoaded, final Runnable onCompleteRunnable) { - - if (mStateAnimation != null) { - mStateAnimation.cancel(); - mStateAnimation = null; - } - Resources res = getResources(); - - final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime); - final int fadeOutDuration = - res.getInteger(R.integer.config_appsCustomizeFadeOutTime); - final float scaleFactor = (float) - res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); - final View fromView = mAppsCustomizeTabHost; - final View toView = mWorkspace; - Animator workspaceAnim = null; - - if (toState == State.WORKSPACE) { - int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger); - workspaceAnim = mWorkspace.getChangeStateAnimation( - Workspace.State.NORMAL, animated, stagger); - } else if (toState == State.APPS_CUSTOMIZE_SPRING_LOADED) { - workspaceAnim = mWorkspace.getChangeStateAnimation( - Workspace.State.SPRING_LOADED, animated); - } - - setPivotsForZoom(fromView, scaleFactor); - updateWallpaperVisibility(true); - showHotseat(animated); - if (animated) { - final LauncherViewPropertyAnimator scaleAnim = - new LauncherViewPropertyAnimator(fromView); - scaleAnim. - scaleX(scaleFactor).scaleY(scaleFactor). - setDuration(duration). - setInterpolator(new Workspace.ZoomInInterpolator()); - - final ObjectAnimator alphaAnim = ObjectAnimator - .ofFloat(fromView, "alpha", 1f, 0f) - .setDuration(fadeOutDuration); - alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator()); - alphaAnim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = 1f - (Float) animation.getAnimatedValue(); - dispatchOnLauncherTransitionStep(fromView, t); - dispatchOnLauncherTransitionStep(toView, t); - } - }); - - mStateAnimation = new AnimatorSet(); - - dispatchOnLauncherTransitionPrepare(fromView, animated, true); - dispatchOnLauncherTransitionPrepare(toView, animated, true); - - mStateAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - updateWallpaperVisibility(true); - fromView.setVisibility(View.GONE); - dispatchOnLauncherTransitionEnd(fromView, animated, true); - dispatchOnLauncherTransitionEnd(toView, animated, true); - if (mWorkspace != null) { - mWorkspace.hideScrollingIndicator(false); - } - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - } - }); - - mStateAnimation.playTogether(scaleAnim, alphaAnim); - if (workspaceAnim != null) { - mStateAnimation.play(workspaceAnim); - } - dispatchOnLauncherTransitionStart(fromView, animated, true); - dispatchOnLauncherTransitionStart(toView, animated, true); - final Animator stateAnimation = mStateAnimation; - mWorkspace.post(new Runnable() { - public void run() { - if (stateAnimation != mStateAnimation) - return; - mStateAnimation.start(); - } - }); - } else { - fromView.setVisibility(View.GONE); - dispatchOnLauncherTransitionPrepare(fromView, animated, true); - dispatchOnLauncherTransitionStart(fromView, animated, true); - dispatchOnLauncherTransitionEnd(fromView, animated, true); - dispatchOnLauncherTransitionPrepare(toView, animated, true); - dispatchOnLauncherTransitionStart(toView, animated, true); - dispatchOnLauncherTransitionEnd(toView, animated, true); - mWorkspace.hideScrollingIndicator(false); - } - } - - @Override - public void onTrimMemory(int level) { - super.onTrimMemory(level); - if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { - mAppsCustomizeTabHost.onTrimMemory(); - } - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - if (!hasFocus) { - // When another window occludes launcher (like the notification shade, or recents), - // ensure that we enable the wallpaper flag so that transitions are done correctly. - updateWallpaperVisibility(true); - } else { - // When launcher has focus again, disable the wallpaper if we are in AllApps - mWorkspace.postDelayed(new Runnable() { - @Override - public void run() { - disableWallpaperIfInAllApps(); - } - }, 500); - } - } - - void showWorkspace(boolean animated) { - showWorkspace(animated, null); - } - - void showWorkspace(boolean animated, Runnable onCompleteRunnable) { - if (mState != State.WORKSPACE) { - boolean wasInSpringLoadedMode = (mState == State.APPS_CUSTOMIZE_SPRING_LOADED); - mWorkspace.setVisibility(View.VISIBLE); - hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable); - - // Show the search bar (only animate if we were showing the drop target bar in spring - // loaded mode) - mSearchDropTargetBar.showSearchBar(wasInSpringLoadedMode); - - // We only need to animate in the dock divider if we're going from spring loaded mode - showDockDivider(animated && wasInSpringLoadedMode); - - // Set focus to the AppsCustomize button - if (mAllAppsButton != null) { - mAllAppsButton.requestFocus(); - } - } - - mWorkspace.flashScrollingIndicator(animated); - - // Change the state *after* we've called all the transition code - mState = State.WORKSPACE; - - // Resume the auto-advance of widgets - mUserPresent = true; - updateRunning(); - - // send an accessibility event to announce the context change - getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - - void showAllApps(boolean animated) { - if (mState != State.WORKSPACE) return; - - showAppsCustomizeHelper(animated, false); - mAppsCustomizeTabHost.requestFocus(); - - // Change the state *after* we've called all the transition code - mState = State.APPS_CUSTOMIZE; - - // Pause the auto-advance of widgets until we are out of AllApps - mUserPresent = false; - updateRunning(); - closeFolder(); - - // Send an accessibility event to announce the context change - getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - - void enterSpringLoadedDragMode() { - if (mState == State.APPS_CUSTOMIZE) { - hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null); - hideDockDivider(); - mState = State.APPS_CUSTOMIZE_SPRING_LOADED; - } - } - - void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay, - final Runnable onCompleteRunnable) { - if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return; - - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (successfulDrop) { - // Before we show workspace, hide all apps again because - // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should - // clean up our state transition functions - mAppsCustomizeTabHost.setVisibility(View.GONE); - showWorkspace(true, onCompleteRunnable); - } else { - exitSpringLoadedDragMode(); - } - } - }, (extendedDelay ? - EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT : - EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT)); - } - - void exitSpringLoadedDragMode() { - if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) { - final boolean animated = true; - final boolean springLoaded = true; - showAppsCustomizeHelper(animated, springLoaded); - mState = State.APPS_CUSTOMIZE; - } - // Otherwise, we are not in spring loaded mode, so don't do anything. - } - - void hideDockDivider() { - if (mQsbDivider != null && mDockDivider != null) { - mQsbDivider.setVisibility(View.INVISIBLE); - mDockDivider.setVisibility(View.INVISIBLE); - } - } - - void showDockDivider(boolean animated) { - if (mQsbDivider != null && mDockDivider != null) { - mQsbDivider.setVisibility(View.VISIBLE); - mDockDivider.setVisibility(View.VISIBLE); - if (mDividerAnimator != null) { - mDividerAnimator.cancel(); - mQsbDivider.setAlpha(1f); - mDockDivider.setAlpha(1f); - mDividerAnimator = null; - } - if (animated) { - mDividerAnimator = new AnimatorSet(); - mDividerAnimator.playTogether(ObjectAnimator.ofFloat(mQsbDivider, "alpha", 1f), - ObjectAnimator.ofFloat(mDockDivider, "alpha", 1f)); - mDividerAnimator.setDuration(mSearchDropTargetBar.getTransitionInDuration()); - mDividerAnimator.start(); - } - } - } - - void lockAllApps() { - // TODO - } - - void unlockAllApps() { - // TODO - } - - public boolean isAllAppsCustomizeOpen() { - return mState == State.APPS_CUSTOMIZE; - } - - /** - * Shows the hotseat area. - */ - void showHotseat(boolean animated) { - if (!LauncherApplication.isScreenLarge()) { - if (animated) { - if (mHotseat.getAlpha() != 1f) { - int duration = mSearchDropTargetBar.getTransitionInDuration(); - mHotseat.animate().alpha(1f).setDuration(duration); - } - } else { - mHotseat.setAlpha(1f); - } - } - } - - /** - * Hides the hotseat area. - */ - void hideHotseat(boolean animated) { - if (!LauncherApplication.isScreenLarge()) { - if (animated) { - if (mHotseat.getAlpha() != 0f) { - int duration = mSearchDropTargetBar.getTransitionOutDuration(); - mHotseat.animate().alpha(0f).setDuration(duration); - } - } else { - mHotseat.setAlpha(0f); - } - } - } - - /** - * Add an item from all apps or customize onto the given workspace screen. - * If layout is null, add to the current screen. - */ - void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) { - if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) { - showOutOfSpaceMessage(isHotseatLayout(layout)); - } - } - - /** Maps the current orientation to an index for referencing orientation correct global icons */ - private int getCurrentOrientationIndexForGlobalIcons() { - // default - 0, landscape - 1 - switch (getResources().getConfiguration().orientation) { - case Configuration.ORIENTATION_LANDSCAPE: - return 1; - default: - return 0; - } - } - - private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) { - 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(resourceName); - if (iconResId != 0) { - Resources res = packageManager.getResourcesForActivity(activityName); - return res.getDrawable(iconResId); - } - } - } catch (NameNotFoundException e) { - // This can happen if the activity defines an invalid drawable - Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() + - " not found", e); - } catch (Resources.NotFoundException nfe) { - // This can happen if the activity defines an invalid drawable - Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(), - nfe); - } - return null; - } - - // if successful in getting icon, return it; otherwise, set button to use default drawable - private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity( - int buttonId, ComponentName activityName, int fallbackDrawableId, - String toolbarResourceName) { - Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName); - Resources r = getResources(); - int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width); - int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height); - - TextView button = (TextView) findViewById(buttonId); - // If we were unable to find the icon via the meta-data, use a generic one - if (toolbarIcon == null) { - toolbarIcon = r.getDrawable(fallbackDrawableId); - toolbarIcon.setBounds(0, 0, w, h); - if (button != null) { - button.setCompoundDrawables(toolbarIcon, null, null, null); - } - return null; - } else { - toolbarIcon.setBounds(0, 0, w, h); - if (button != null) { - button.setCompoundDrawables(toolbarIcon, null, null, null); - } - return toolbarIcon.getConstantState(); - } - } - - // if successful in getting icon, return it; otherwise, set button to use default drawable - private Drawable.ConstantState updateButtonWithIconFromExternalActivity( - int buttonId, ComponentName activityName, int fallbackDrawableId, - String toolbarResourceName) { - ImageView button = (ImageView) findViewById(buttonId); - Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName); - - if (button != null) { - // 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); - } - } - - return toolbarIcon != null ? toolbarIcon.getConstantState() : null; - - } - - private void updateTextButtonWithDrawable(int buttonId, Drawable d) { - TextView button = (TextView) findViewById(buttonId); - button.setCompoundDrawables(d, null, null, null); - } - - private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) { - ImageView button = (ImageView) findViewById(buttonId); - button.setImageDrawable(d.newDrawable(getResources())); - } - - private void invalidatePressedFocusedStates(View container, View button) { - if (container instanceof HolographicLinearLayout) { - HolographicLinearLayout layout = (HolographicLinearLayout) container; - layout.invalidatePressedFocusedStates(); - } else if (button instanceof HolographicImageView) { - HolographicImageView view = (HolographicImageView) button; - view.invalidatePressedFocusedStates(); - } - } - - private boolean updateGlobalSearchIcon() { - final View searchButtonContainer = findViewById(R.id.search_button_container); - final ImageView searchButton = (ImageView) findViewById(R.id.search_button); - final View searchDivider = findViewById(R.id.search_divider); - final View voiceButtonContainer = findViewById(R.id.voice_button_container); - final View voiceButton = findViewById(R.id.voice_button); - final View voiceButtonProxy = findViewById(R.id.voice_button_proxy); - - final SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - ComponentName activityName = searchManager.getGlobalSearchActivity(); - if (activityName != null) { - int coi = getCurrentOrientationIndexForGlobalIcons(); - sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity( - R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo, - TOOLBAR_SEARCH_ICON_METADATA_NAME); - if (sGlobalSearchIcon[coi] == null) { - sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity( - R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo, - TOOLBAR_ICON_METADATA_NAME); - } - - if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE); - if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE); - searchButton.setVisibility(View.VISIBLE); - invalidatePressedFocusedStates(searchButtonContainer, searchButton); - return true; - } else { - // We disable both search and voice search when there is no global search provider - if (searchDivider != null) searchDivider.setVisibility(View.GONE); - if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE); - if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE); - searchButton.setVisibility(View.GONE); - voiceButton.setVisibility(View.GONE); - if (voiceButtonProxy != null) { - voiceButtonProxy.setVisibility(View.GONE); - } - return false; - } - } - - private void updateGlobalSearchIcon(Drawable.ConstantState d) { - final View searchButtonContainer = findViewById(R.id.search_button_container); - final View searchButton = (ImageView) findViewById(R.id.search_button); - updateButtonWithDrawable(R.id.search_button, d); - invalidatePressedFocusedStates(searchButtonContainer, searchButton); - } - - private boolean updateVoiceSearchIcon(boolean searchVisible) { - final View searchDivider = findViewById(R.id.search_divider); - final View voiceButtonContainer = findViewById(R.id.voice_button_container); - final View voiceButton = findViewById(R.id.voice_button); - final View voiceButtonProxy = findViewById(R.id.voice_button_proxy); - - // We only show/update the voice search icon if the search icon is enabled as well - final SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity(); - - ComponentName activityName = null; - if (globalSearchActivity != null) { - // Check if the global search activity handles voice search - Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); - intent.setPackage(globalSearchActivity.getPackageName()); - activityName = intent.resolveActivity(getPackageManager()); - } - - if (activityName == null) { - // Fallback: check if an activity other than the global search activity - // resolves this - Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); - activityName = intent.resolveActivity(getPackageManager()); - } - if (searchVisible && activityName != null) { - int coi = getCurrentOrientationIndexForGlobalIcons(); - sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity( - R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo, - TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME); - if (sVoiceSearchIcon[coi] == null) { - sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity( - R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo, - TOOLBAR_ICON_METADATA_NAME); - } - if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE); - if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE); - voiceButton.setVisibility(View.VISIBLE); - if (voiceButtonProxy != null) { - voiceButtonProxy.setVisibility(View.VISIBLE); - } - invalidatePressedFocusedStates(voiceButtonContainer, voiceButton); - return true; - } else { - if (searchDivider != null) searchDivider.setVisibility(View.GONE); - if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE); - voiceButton.setVisibility(View.GONE); - if (voiceButtonProxy != null) { - voiceButtonProxy.setVisibility(View.GONE); - } - return false; - } - } - - private void updateVoiceSearchIcon(Drawable.ConstantState d) { - final View voiceButtonContainer = findViewById(R.id.voice_button_container); - final View voiceButton = findViewById(R.id.voice_button); - updateButtonWithDrawable(R.id.voice_button, d); - invalidatePressedFocusedStates(voiceButtonContainer, voiceButton); - } - - /** - * Sets the app market icon - */ - private void updateAppMarketIcon() { - final View marketButton = findViewById(R.id.market_button); - 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) { - int coi = getCurrentOrientationIndexForGlobalIcons(); - mAppMarketIntent = intent; - sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity( - R.id.market_button, activityName, R.drawable.ic_launcher_market_holo, - TOOLBAR_ICON_METADATA_NAME); - marketButton.setVisibility(View.VISIBLE); - } else { - // We should hide and disable the view so that we don't try and restore the visibility - // of it when we swap between drag & normal states from IconDropTarget subclasses. - marketButton.setVisibility(View.GONE); - marketButton.setEnabled(false); - } - } - - private void updateAppMarketIcon(Drawable.ConstantState d) { - // Ensure that the new drawable we are creating has the approprate toolbar icon bounds - Resources r = getResources(); - Drawable marketIconDrawable = d.newDrawable(r); - int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width); - int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height); - marketIconDrawable.setBounds(0, 0, w, h); - - updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - boolean result = super.dispatchPopulateAccessibilityEvent(event); - final List text = event.getText(); - text.clear(); - text.add(getString(R.string.home)); - return result; - } - - /** - * Receives notifications when system dialogs are to be closed. - */ - private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - closeSystemDialogs(); - } - } - - /** - * Receives notifications whenever the appwidgets are reset. - */ - private class AppWidgetResetObserver extends ContentObserver { - public AppWidgetResetObserver() { - super(new Handler()); - } - - @Override - public void onChange(boolean selfChange) { - onAppWidgetReset(); - } - } - - /** - * If the activity is currently paused, signal that we need to re-run the loader - * in onResume. - * - * This needs to be called from incoming places where resources might have been loaded - * while we are paused. That is becaues the Configuration might be wrong - * when we're not running, and if it comes back to what it was when we - * were paused, we are not restarted. - * - * Implementation of the method from LauncherModel.Callbacks. - * - * @return true if we are currently paused. The caller might be able to - * skip some work in that case since we will come back again. - */ - public boolean setLoadOnResume() { - if (mPaused) { - Log.i(TAG, "setLoadOnResume"); - mOnResumeNeedsLoad = true; - return true; - } else { - return false; - } - } - - /** - * Implementation of the method from LauncherModel.Callbacks. - */ - public int getCurrentWorkspaceScreen() { - if (mWorkspace != null) { - return mWorkspace.getCurrentPage(); - } else { - return SCREEN_COUNT / 2; - } - } - - /** - * Refreshes the shortcuts shown on the workspace. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void startBinding() { - final Workspace workspace = mWorkspace; - - mNewShortcutAnimatePage = -1; - mNewShortcutAnimateViews.clear(); - mWorkspace.clearDropTargets(); - int count = workspace.getChildCount(); - for (int i = 0; i < count; i++) { - // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate(). - final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i); - layoutParent.removeAllViewsInLayout(); - } - mWidgetsToAdvance.clear(); - if (mHotseat != null) { - mHotseat.resetLayout(); - } - } - - /** - * Bind the items start-end from the list. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindItems(ArrayList shortcuts, int start, int end) { - setLoadOnResume(); - - // Get the list of added shortcuts and intersect them with the set of shortcuts here - Set newApps = new HashSet(); - newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps); - - Workspace workspace = mWorkspace; - for (int i = start; i < end; i++) { - final ItemInfo item = shortcuts.get(i); - - // Short circuit if we are loading dock items for a configuration which has no dock - if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && - mHotseat == null) { - continue; - } - - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - ShortcutInfo info = (ShortcutInfo) item; - String uri = info.intent.toUri(0).toString(); - View shortcut = createShortcut(info); - workspace.addInScreen(shortcut, item.container, item.screen, item.cellX, - item.cellY, 1, 1, false); - boolean animateIconUp = false; - synchronized (newApps) { - if (newApps.contains(uri)) { - animateIconUp = newApps.remove(uri); - } - } - if (animateIconUp) { - // Prepare the view to be animated up - shortcut.setAlpha(0f); - shortcut.setScaleX(0f); - shortcut.setScaleY(0f); - mNewShortcutAnimatePage = item.screen; - if (!mNewShortcutAnimateViews.contains(shortcut)) { - mNewShortcutAnimateViews.add(shortcut); - } - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), - (FolderInfo) item, mIconCache); - workspace.addInScreen(newFolder, item.container, item.screen, item.cellX, - item.cellY, 1, 1, false); - break; - } - } - - workspace.requestLayout(); - } - - /** - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindFolders(HashMap folders) { - setLoadOnResume(); - sFolders.clear(); - sFolders.putAll(folders); - } - - /** - * Add the views for a widget to the workspace. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindAppWidget(LauncherAppWidgetInfo item) { - setLoadOnResume(); - - final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; - if (DEBUG_WIDGETS) { - Log.d(TAG, "bindAppWidget: " + item); - } - final Workspace workspace = mWorkspace; - - final int appWidgetId = item.appWidgetId; - final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); - if (DEBUG_WIDGETS) { - Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); - } - - item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); - - item.hostView.setTag(item); - item.onBindAppWidget(this); - - workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX, - item.cellY, item.spanX, item.spanY, false); - addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo); - - workspace.requestLayout(); - - if (DEBUG_WIDGETS) { - Log.d(TAG, "bound widget id="+item.appWidgetId+" in " - + (SystemClock.uptimeMillis()-start) + "ms"); - } - } - - /** - * Callback saying that there aren't any more items to bind. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void finishBindingItems() { - setLoadOnResume(); - - if (mSavedState != null) { - if (!mWorkspace.hasFocus()) { - mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); - } - mSavedState = null; - } - - if (mSavedInstanceState != null) { - super.onRestoreInstanceState(mSavedInstanceState); - mSavedInstanceState = null; - } - - // If we received the result of any pending adds while the loader was running (e.g. the - // widget configuration forced an orientation change), process them now. - for (int i = 0; i < sPendingAddList.size(); i++) { - completeAdd(sPendingAddList.get(i)); - } - sPendingAddList.clear(); - - // Update the market app icon as necessary (the other icons will be managed in response to - // package changes in bindSearchablesChanged() - updateAppMarketIcon(); - - // Animate up any icons as necessary - if (mVisible || mWorkspaceLoading) { - Runnable newAppsRunnable = new Runnable() { - @Override - public void run() { - runNewAppsAnimation(false); - } - }; - - boolean willSnapPage = mNewShortcutAnimatePage > -1 && - mNewShortcutAnimatePage != mWorkspace.getCurrentPage(); - if (canRunNewAppsAnimation()) { - // If the user has not interacted recently, then either snap to the new page to show - // the new-apps animation or just run them if they are to appear on the current page - if (willSnapPage) { - mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable); - } else { - runNewAppsAnimation(false); - } - } else { - // If the user has interacted recently, then just add the items in place if they - // are on another page (or just normally if they are added to the current page) - runNewAppsAnimation(willSnapPage); - } - } - - mWorkspaceLoading = false; - } - - private boolean canRunNewAppsAnimation() { - long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); - return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); - } - - /** - * Runs a new animation that scales up icons that were added while Launcher was in the - * background. - * - * @param immediate whether to run the animation or show the results immediately - */ - private void runNewAppsAnimation(boolean immediate) { - AnimatorSet anim = new AnimatorSet(); - Collection bounceAnims = new ArrayList(); - - // Order these new views spatially so that they animate in order - Collections.sort(mNewShortcutAnimateViews, new Comparator() { - @Override - public int compare(View a, View b) { - CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams(); - CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams(); - int cellCountX = LauncherModel.getCellCountX(); - return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX); - } - }); - - // Animate each of the views in place (or show them immediately if requested) - if (immediate) { - for (View v : mNewShortcutAnimateViews) { - v.setAlpha(1f); - v.setScaleX(1f); - v.setScaleY(1f); - } - } else { - for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) { - View v = mNewShortcutAnimateViews.get(i); - ValueAnimator bounceAnim = ObjectAnimator.ofPropertyValuesHolder(v, - PropertyValuesHolder.ofFloat("alpha", 1f), - PropertyValuesHolder.ofFloat("scaleX", 1f), - PropertyValuesHolder.ofFloat("scaleY", 1f)); - bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); - bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); - bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator()); - bounceAnims.add(bounceAnim); - } - anim.playTogether(bounceAnims); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mWorkspace.postDelayed(mBuildLayersRunnable, 500); - } - }); - anim.start(); - } - - // Clean up - mNewShortcutAnimatePage = -1; - mNewShortcutAnimateViews.clear(); - new Thread("clearNewAppsThread") { - public void run() { - mSharedPrefs.edit() - .putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1) - .putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null) - .commit(); - } - }.start(); - } - - @Override - public void bindSearchablesChanged() { - boolean searchVisible = updateGlobalSearchIcon(); - boolean voiceVisible = updateVoiceSearchIcon(searchVisible); - mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); - } - - /** - * Add the icons for all apps. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindAllApplications(final ArrayList apps) { - // Remove the progress bar entirely; we could also make it GONE - // but better to remove it since we know it's not going to be used - View progressBar = mAppsCustomizeTabHost. - findViewById(R.id.apps_customize_progress_bar); - if (progressBar != null) { - ((ViewGroup)progressBar.getParent()).removeView(progressBar); - } - // We just post the call to setApps so the user sees the progress bar - // disappear-- otherwise, it just looks like the progress bar froze - // which doesn't look great - mAppsCustomizeTabHost.post(new Runnable() { - public void run() { - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.setApps(apps); - } - } - }); - } - - /** - * A package was installed. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindAppsAdded(ArrayList apps) { - setLoadOnResume(); - - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.addApps(apps); - } - } - - /** - * A package was updated. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindAppsUpdated(ArrayList apps) { - setLoadOnResume(); - if (mWorkspace != null) { - mWorkspace.updateShortcuts(apps); - } - - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.updateApps(apps); - } - } - - /** - * A package was uninstalled. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - public void bindAppsRemoved(ArrayList apps, boolean permanent) { - if (permanent) { - mWorkspace.removeItems(apps); - } - - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.removeApps(apps); - } - - // Notify the drag controller - mDragController.onAppsRemoved(apps, this); - } - - /** - * A number of packages were updated. - */ - public void bindPackagesUpdated() { - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.onPackagesUpdated(); - } - } - - private int mapConfigurationOriActivityInfoOri(int configOri) { - final Display d = getWindowManager().getDefaultDisplay(); - int naturalOri = Configuration.ORIENTATION_LANDSCAPE; - switch (d.getRotation()) { - case Surface.ROTATION_0: - case Surface.ROTATION_180: - // We are currently in the same basic orientation as the natural orientation - naturalOri = configOri; - break; - case Surface.ROTATION_90: - case Surface.ROTATION_270: - // We are currently in the other basic orientation to the natural orientation - naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ? - Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; - break; - } - - int[] oriMap = { - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, - ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, - ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, - ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - }; - // Since the map starts at portrait, we need to offset if this device's natural orientation - // is landscape. - int indexOffset = 0; - if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) { - indexOffset = 1; - } - return oriMap[(d.getRotation() + indexOffset) % 4]; - } - - public boolean isRotationEnabled() { - boolean forceEnableRotation = "true".equalsIgnoreCase(SystemProperties.get( - FORCE_ENABLE_ROTATION_PROPERTY, "false")); - boolean enableRotation = forceEnableRotation || - getResources().getBoolean(R.bool.allow_rotation); - return enableRotation; - } - public void lockScreenOrientation() { - if (isRotationEnabled()) { - setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources() - .getConfiguration().orientation)); - } - } - public void unlockScreenOrientation(boolean immediate) { - if (isRotationEnabled()) { - if (immediate) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } else { - mHandler.postDelayed(new Runnable() { - public void run() { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } - }, mRestoreScreenOrientationDelay); - } - } - } - - /* Cling related */ - private boolean isClingsEnabled() { - // disable clings when running in a test harness - if(ActivityManager.isRunningInTestHarness()) return false; - - return true; - } - private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) { - Cling cling = (Cling) findViewById(clingId); - if (cling != null) { - cling.init(this, positionData); - cling.setVisibility(View.VISIBLE); - cling.setLayerType(View.LAYER_TYPE_HARDWARE, null); - cling.requestAccessibilityFocus(); - if (animate) { - cling.buildLayer(); - cling.setAlpha(0f); - cling.animate() - .alpha(1f) - .setInterpolator(new AccelerateInterpolator()) - .setDuration(SHOW_CLING_DURATION) - .setStartDelay(delay) - .start(); - } else { - cling.setAlpha(1f); - } - } - return cling; - } - private void dismissCling(final Cling cling, final String flag, int duration) { - if (cling != null) { - ObjectAnimator anim = ObjectAnimator.ofFloat(cling, "alpha", 0f); - anim.setDuration(duration); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - cling.setVisibility(View.GONE); - cling.cleanup(); - // We should update the shared preferences on a background thread - new Thread("dismissClingThread") { - public void run() { - SharedPreferences.Editor editor = mSharedPrefs.edit(); - editor.putBoolean(flag, true); - editor.commit(); - } - }.start(); - }; - }); - anim.start(); - } - } - private void removeCling(int id) { - final View cling = findViewById(id); - if (cling != null) { - final ViewGroup parent = (ViewGroup) cling.getParent(); - parent.post(new Runnable() { - @Override - public void run() { - parent.removeView(cling); - } - }); - } - } - - private boolean skipCustomClingIfNoAccounts() { - Cling cling = (Cling) findViewById(R.id.workspace_cling); - boolean customCling = cling.getDrawIdentifier().equals("workspace_custom"); - if (customCling) { - AccountManager am = AccountManager.get(this); - Account[] accounts = am.getAccountsByType("com.google"); - return accounts.length == 0; - } - return false; - } - - public void showFirstRunWorkspaceCling() { - // Enable the clings only if they have not been dismissed before - if (isClingsEnabled() && - !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false) && - !skipCustomClingIfNoAccounts() ) { - initCling(R.id.workspace_cling, null, false, 0); - } else { - removeCling(R.id.workspace_cling); - } - } - public void showFirstRunAllAppsCling(int[] position) { - // Enable the clings only if they have not been dismissed before - if (isClingsEnabled() && - !mSharedPrefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) { - initCling(R.id.all_apps_cling, position, true, 0); - } else { - removeCling(R.id.all_apps_cling); - } - } - public Cling showFirstRunFoldersCling() { - // Enable the clings only if they have not been dismissed before - if (isClingsEnabled() && - !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) { - return initCling(R.id.folder_cling, null, true, 0); - } else { - removeCling(R.id.folder_cling); - return null; - } - } - public boolean isFolderClingVisible() { - Cling cling = (Cling) findViewById(R.id.folder_cling); - if (cling != null) { - return cling.getVisibility() == View.VISIBLE; - } - return false; - } - public void dismissWorkspaceCling(View v) { - Cling cling = (Cling) findViewById(R.id.workspace_cling); - dismissCling(cling, Cling.WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); - } - public void dismissAllAppsCling(View v) { - Cling cling = (Cling) findViewById(R.id.all_apps_cling); - dismissCling(cling, Cling.ALLAPPS_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); - } - public void dismissFolderCling(View v) { - Cling cling = (Cling) findViewById(R.id.folder_cling); - dismissCling(cling, Cling.FOLDER_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); - } - - /** - * Prints out out state for debugging. - */ - public void dumpState() { - Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this); - Log.d(TAG, "mSavedState=" + mSavedState); - Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); - Log.d(TAG, "mRestoring=" + mRestoring); - Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); - Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState); - Log.d(TAG, "sFolders.size=" + sFolders.size()); - mModel.dumpState(); - - if (mAppsCustomizeContent != null) { - mAppsCustomizeContent.dumpState(); - } - Log.d(TAG, "END launcher2 dump state"); - } - - @Override - public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - super.dump(prefix, fd, writer, args); - writer.println(" "); - writer.println("Debug logs: "); - for (int i = 0; i < sDumpLogs.size(); i++) { - writer.println(" " + sDumpLogs.get(i)); - } - } -} - -interface LauncherTransitionable { - View getContent(); - void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace); - void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace); - void onLauncherTransitionStep(Launcher l, float t); - void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace); -} diff --git a/src/com/android/launcher2/LauncherAnimatorUpdateListener.java b/src/com/android/launcher2/LauncherAnimatorUpdateListener.java deleted file mode 100644 index dd821134d..000000000 --- a/src/com/android/launcher2/LauncherAnimatorUpdateListener.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; - -abstract class LauncherAnimatorUpdateListener implements AnimatorUpdateListener { - public void onAnimationUpdate(ValueAnimator animation) { - final float b = (Float) animation.getAnimatedValue(); - final float a = 1f - b; - onAnimationUpdate(a, b); - } - - abstract void onAnimationUpdate(float a, float b); -} \ No newline at end of file diff --git a/src/com/android/launcher2/LauncherAppWidgetHost.java b/src/com/android/launcher2/LauncherAppWidgetHost.java deleted file mode 100644 index 68d4903da..000000000 --- a/src/com/android/launcher2/LauncherAppWidgetHost.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetProviderInfo; -import android.content.Context; - -/** - * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} - * which correctly captures all long-press events. This ensures that users can - * always pick up and move widgets. - */ -public class LauncherAppWidgetHost extends AppWidgetHost { - public LauncherAppWidgetHost(Context context, int hostId) { - super(context, hostId); - } - - @Override - protected AppWidgetHostView onCreateView(Context context, int appWidgetId, - AppWidgetProviderInfo appWidget) { - return new LauncherAppWidgetHostView(context); - } - - @Override - public void stopListening() { - super.stopListening(); - clearViews(); - } -} diff --git a/src/com/android/launcher2/LauncherAppWidgetHostView.java b/src/com/android/launcher2/LauncherAppWidgetHostView.java deleted file mode 100644 index 9970c7675..000000000 --- a/src/com/android/launcher2/LauncherAppWidgetHostView.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.appwidget.AppWidgetHostView; -import android.content.Context; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.Parcel; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RemoteViews; - -import com.android.launcher.R; - -/** - * {@inheritDoc} - */ -public class LauncherAppWidgetHostView extends AppWidgetHostView { - private CheckLongPressHelper mLongPressHelper; - private LayoutInflater mInflater; - private Context mContext; - private int mPreviousOrientation; - - public LauncherAppWidgetHostView(Context context) { - super(context); - mContext = context; - mLongPressHelper = new CheckLongPressHelper(this); - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - protected View getErrorView() { - return mInflater.inflate(R.layout.appwidget_error, this, false); - } - - @Override - public void updateAppWidget(RemoteViews remoteViews) { - // Store the orientation in which the widget was inflated - mPreviousOrientation = mContext.getResources().getConfiguration().orientation; - super.updateAppWidget(remoteViews); - } - - public boolean orientationChangedSincedInflation() { - int orientation = mContext.getResources().getConfiguration().orientation; - if (mPreviousOrientation != orientation) { - return true; - } - return false; - } - - public boolean onInterceptTouchEvent(MotionEvent ev) { - // Consume any touch events for ourselves after longpress is triggered - if (mLongPressHelper.hasPerformedLongPress()) { - mLongPressHelper.cancelLongPress(); - return true; - } - - // Watch for longpress events at this level to make sure - // users can always pick up this widget - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: { - mLongPressHelper.postCheckForLongPress(); - break; - } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mLongPressHelper.cancelLongPress(); - break; - } - - // Otherwise continue letting touch events fall through to children - return false; - } - - @Override - public void cancelLongPress() { - super.cancelLongPress(); - - mLongPressHelper.cancelLongPress(); - } - - @Override - public int getDescendantFocusability() { - return ViewGroup.FOCUS_BLOCK_DESCENDANTS; - } -} diff --git a/src/com/android/launcher2/LauncherAppWidgetInfo.java b/src/com/android/launcher2/LauncherAppWidgetInfo.java deleted file mode 100644 index f001b2b64..000000000 --- a/src/com/android/launcher2/LauncherAppWidgetInfo.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.appwidget.AppWidgetHostView; -import android.content.ComponentName; -import android.content.ContentValues; - -/** - * 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 = NO_ID; - - ComponentName providerName; - - // TODO: Are these necessary here? - int minWidth = -1; - int minHeight = -1; - - private boolean mHasNotifiedInitialWidgetSizeChanged; - - /** - * View that holds this widget after it's been created. This view isn't created - * until Launcher knows it's needed. - */ - AppWidgetHostView hostView = null; - - LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { - itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; - this.appWidgetId = appWidgetId; - 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; - } - - @Override - void onAddToDatabase(ContentValues values) { - super.onAddToDatabase(values); - values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); - } - - /** - * When we bind the widget, we should notify the widget that the size has changed if we have not - * done so already (only really for default workspace widgets). - */ - void onBindAppWidget(Launcher launcher) { - if (!mHasNotifiedInitialWidgetSizeChanged) { - notifyWidgetSizeChanged(launcher); - } - } - - /** - * Trigger an update callback to the widget to notify it that its size has changed. - */ - void notifyWidgetSizeChanged(Launcher launcher) { - AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY); - mHasNotifiedInitialWidgetSizeChanged = true; - } - - @Override - public String toString() { - return "AppWidget(id=" + Integer.toString(appWidgetId) + ")"; - } - - @Override - void unbind() { - super.unbind(); - hostView = null; - } -} diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java deleted file mode 100644 index 28362fd0b..000000000 --- a/src/com/android/launcher2/LauncherApplication.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.app.Application; -import android.app.SearchManager; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.database.ContentObserver; -import android.os.Handler; - -import com.android.launcher.R; - -import java.lang.ref.WeakReference; - -public class LauncherApplication extends Application { - public LauncherModel mModel; - public IconCache mIconCache; - private static boolean sIsScreenLarge; - private static float sScreenDensity; - private static int sLongPressTimeout = 300; - private static final String sSharedPreferencesKey = "com.android.launcher2.prefs"; - WeakReference mLauncherProvider; - - @Override - public void onCreate() { - super.onCreate(); - - // set sIsScreenXLarge and sScreenDensity *before* creating icon cache - sIsScreenLarge = getResources().getBoolean(R.bool.is_large_screen); - sScreenDensity = getResources().getDisplayMetrics().density; - - mIconCache = new IconCache(this); - mModel = new LauncherModel(this, mIconCache); - - // Register intent receivers - IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addDataScheme("package"); - registerReceiver(mModel, filter); - filter = new IntentFilter(); - filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); - filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - filter.addAction(Intent.ACTION_LOCALE_CHANGED); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - registerReceiver(mModel, filter); - filter = new IntentFilter(); - filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); - registerReceiver(mModel, filter); - filter = new IntentFilter(); - filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); - registerReceiver(mModel, filter); - - // Register for changes to the favorites - ContentResolver resolver = getContentResolver(); - resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, - mFavoritesObserver); - } - - /** - * There's no guarantee that this function is ever called. - */ - @Override - public void onTerminate() { - super.onTerminate(); - - unregisterReceiver(mModel); - - ContentResolver resolver = getContentResolver(); - resolver.unregisterContentObserver(mFavoritesObserver); - } - - /** - * Receives notifications whenever the user favorites have changed. - */ - private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - // If the database has ever changed, then we really need to force a reload of the - // workspace on the next load - mModel.resetLoadedState(false, true); - mModel.startLoaderFromBackground(); - } - }; - - LauncherModel setLauncher(Launcher launcher) { - mModel.initialize(launcher); - return mModel; - } - - IconCache getIconCache() { - return mIconCache; - } - - LauncherModel getModel() { - return mModel; - } - - void setLauncherProvider(LauncherProvider provider) { - mLauncherProvider = new WeakReference(provider); - } - - LauncherProvider getLauncherProvider() { - return mLauncherProvider.get(); - } - - public static String getSharedPreferencesKey() { - return sSharedPreferencesKey; - } - - public static boolean isScreenLarge() { - return sIsScreenLarge; - } - - public static boolean isScreenLandscape(Context context) { - return context.getResources().getConfiguration().orientation == - Configuration.ORIENTATION_LANDSCAPE; - } - - public static float getScreenDensity() { - return sScreenDensity; - } - - public static int getLongPressTimeout() { - return sLongPressTimeout; - } -} diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java deleted file mode 100644 index fc1a26d4b..000000000 --- a/src/com/android/launcher2/LauncherModel.java +++ /dev/null @@ -1,2176 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.app.SearchManager; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.BroadcastReceiver; -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.pm.ActivityInfo; -import android.content.pm.PackageInfo; -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.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Parcelable; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Log; - -import com.android.launcher.R; -import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData; - -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; - -/** - * Maintains in-memory state of the Launcher. It is expected that there should be only one - * LauncherModel object held in a static. Also provide APIs for updating the database state - * for the Launcher. - */ -public class LauncherModel extends BroadcastReceiver { - static final boolean DEBUG_LOADERS = false; - static final String TAG = "Launcher.Model"; - - private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons - private final boolean mAppsCanBeOnExternalStorage; - private int mBatchSize; // 0 is all apps at once - private int mAllAppsLoadDelay; // milliseconds between batches - - private final LauncherApplication mApp; - private final Object mLock = new Object(); - private DeferredHandler mHandler = new DeferredHandler(); - private LoaderTask mLoaderTask; - - private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); - static { - sWorkerThread.start(); - } - private static final Handler sWorker = new Handler(sWorkerThread.getLooper()); - - // We start off with everything not loaded. After that, we assume that - // our monitoring of the package manager provides all updates and we never - // need to do a requery. These are only ever touched from the loader thread. - private boolean mWorkspaceLoaded; - private boolean mAllAppsLoaded; - - private WeakReference mCallbacks; - - // < only access in worker thread > - private AllAppsList mAllAppsList; - - // sItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by - // LauncherModel to their ids - static final HashMap sItemsIdMap = new HashMap(); - - // sItems is passed to bindItems, which expects a list of all folders and shortcuts created by - // LauncherModel that are directly on the home screen (however, no widgets or shortcuts - // within folders). - static final ArrayList sWorkspaceItems = new ArrayList(); - - // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() - static final ArrayList sAppWidgets = - new ArrayList(); - - // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() - static final HashMap sFolders = new HashMap(); - - // sDbIconCache is the set of ItemInfos that need to have their icons updated in the database - static final HashMap sDbIconCache = new HashMap(); - - // - - private IconCache mIconCache; - private Bitmap mDefaultIcon; - - private static int mCellCountX; - private static int mCellCountY; - - protected int mPreviousConfigMcc; - - public interface Callbacks { - public boolean setLoadOnResume(); - public int getCurrentWorkspaceScreen(); - public void startBinding(); - public void bindItems(ArrayList shortcuts, int start, int end); - public void bindFolders(HashMap folders); - public void finishBindingItems(); - public void bindAppWidget(LauncherAppWidgetInfo info); - public void bindAllApplications(ArrayList apps); - public void bindAppsAdded(ArrayList apps); - public void bindAppsUpdated(ArrayList apps); - public void bindAppsRemoved(ArrayList apps, boolean permanent); - public void bindPackagesUpdated(); - public boolean isAllAppsVisible(); - public boolean isAllAppsButtonRank(int rank); - public void bindSearchablesChanged(); - } - - LauncherModel(LauncherApplication app, IconCache iconCache) { - mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated(); - mApp = app; - mAllAppsList = new AllAppsList(iconCache); - mIconCache = iconCache; - - mDefaultIcon = Utilities.createIconBitmap( - mIconCache.getFullResDefaultActivityIcon(), app); - - final Resources res = app.getResources(); - mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay); - mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize); - Configuration config = res.getConfiguration(); - mPreviousConfigMcc = config.mcc; - } - - public Bitmap getFallbackIcon() { - return Bitmap.createBitmap(mDefaultIcon); - } - - public void unbindWorkspaceItems() { - sWorker.post(new Runnable() { - @Override - public void run() { - unbindWorkspaceItemsOnMainThread(); - } - }); - } - - /** Unbinds all the sWorkspaceItems on the main thread, and return a copy of sWorkspaceItems - * that is save to reference from the main thread. */ - private ArrayList unbindWorkspaceItemsOnMainThread() { - // Ensure that we don't use the same workspace items data structure on the main thread - // by making a copy of workspace items first. - final ArrayList workspaceItems = new ArrayList(sWorkspaceItems); - final ArrayList appWidgets = new ArrayList(sAppWidgets); - mHandler.post(new Runnable() { - @Override - public void run() { - for (ItemInfo item : workspaceItems) { - item.unbind(); - } - for (ItemInfo item : appWidgets) { - item.unbind(); - } - } - }); - - return workspaceItems; - } - - /** - * Adds an item to the DB if it was not created previously, or move it to a new - * - */ - static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, - int screen, int cellX, int cellY) { - if (item.container == ItemInfo.NO_ID) { - // From all apps - addItemToDatabase(context, item, container, screen, cellX, cellY, false); - } else { - // From somewhere else - moveItemInDatabase(context, item, container, screen, cellX, cellY); - } - } - - static void updateItemInDatabaseHelper(Context context, final ContentValues values, - final ItemInfo item, final String callingFunction) { - final long itemId = item.id; - final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false); - final ContentResolver cr = context.getContentResolver(); - - Runnable r = new Runnable() { - public void run() { - cr.update(uri, values, null, null); - - ItemInfo modelItem = sItemsIdMap.get(itemId); - if (item != modelItem) { - // the modelItem needs to match up perfectly with item if our model is to be - // consistent with the database-- for now, just require modelItem == item - String msg = "item: " + ((item != null) ? item.toString() : "null") + - "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") + - "Error: ItemInfo passed to " + callingFunction + " doesn't match original"; - throw new RuntimeException(msg); - } - - // Items are added/removed from the corresponding FolderInfo elsewhere, such - // as in Workspace.onDrop. Here, we just add/remove them from the list of items - // that are on the desktop, as appropriate - if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || - modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - if (!sWorkspaceItems.contains(modelItem)) { - sWorkspaceItems.add(modelItem); - } - } else { - sWorkspaceItems.remove(modelItem); - } - } - }; - - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } - } - - /** - * Move an item in the DB to a new - */ - static void moveItemInDatabase(Context context, final ItemInfo item, final long container, - final int screen, final int cellX, final int cellY) { - item.container = container; - item.cellX = cellX; - item.cellY = cellY; - - // We store hotseat items in canonical form which is this orientation invariant position - // in the hotseat - if (context instanceof Launcher && screen < 0 && - container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); - } else { - item.screen = screen; - } - - final ContentValues values = new ContentValues(); - 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.SCREEN, item.screen); - - updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); - } - - /** - * Move and/or resize item in the DB to a new - */ - static void modifyItemInDatabase(Context context, final ItemInfo item, final long container, - final int screen, final int cellX, final int cellY, final int spanX, final int spanY) { - item.container = container; - item.cellX = cellX; - item.cellY = cellY; - item.spanX = spanX; - item.spanY = spanY; - - // We store hotseat items in canonical form which is this orientation invariant position - // in the hotseat - if (context instanceof Launcher && screen < 0 && - container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); - } else { - item.screen = screen; - } - - final ContentValues values = new ContentValues(); - 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.SPANX, item.spanX); - values.put(LauncherSettings.Favorites.SPANY, item.spanY); - values.put(LauncherSettings.Favorites.SCREEN, item.screen); - - updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); - } - - /** - * Update an item to the database in a specified container. - */ - static void updateItemInDatabase(Context context, final ItemInfo item) { - final ContentValues values = new ContentValues(); - item.onAddToDatabase(values); - item.updateValuesWithCoordinates(values, item.cellX, item.cellY); - updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); - } - - /** - * Returns true if the shortcuts already exists in the database. - * we identify a shortcut by its title and intent. - */ - static boolean shortcutExists(Context context, String title, Intent intent) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { "title", "intent" }, "title=? and intent=?", - new String[] { title, intent.toUri(0) }, null); - boolean result = false; - try { - result = c.moveToFirst(); - } finally { - c.close(); - } - return result; - } - - /** - * Returns an ItemInfo array containing all the items in the LauncherModel. - * The ItemInfo.id is not set through this function. - */ - static ArrayList getItemsInLocalCoordinates(Context context) { - ArrayList items = new ArrayList(); - 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 folderList, long id) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, - "_id=? and (itemType=? or itemType=?)", - new String[] { String.valueOf(id), - String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null); - - try { - if (c.moveToFirst()) { - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - 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); - - FolderInfo folderInfo = null; - switch (c.getInt(itemTypeIndex)) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - folderInfo = findOrMakeFolder(folderList, id); - break; - } - - folderInfo.title = c.getString(titleIndex); - folderInfo.id = id; - folderInfo.container = c.getInt(containerIndex); - folderInfo.screen = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); - - return folderInfo; - } - } finally { - c.close(); - } - - return null; - } - - /** - * Add an item to the database in a specified container. Sets the container, screen, cellX and - * cellY fields of the item. Also assigns an ID to the item. - */ - static void addItemToDatabase(Context context, final ItemInfo item, final long container, - final int screen, final int cellX, final int cellY, final boolean notify) { - item.container = container; - item.cellX = cellX; - item.cellY = cellY; - // We store hotseat items in canonical form which is this orientation invariant position - // in the hotseat - if (context instanceof Launcher && screen < 0 && - container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); - } else { - item.screen = screen; - } - - final ContentValues values = new ContentValues(); - final ContentResolver cr = context.getContentResolver(); - item.onAddToDatabase(values); - - LauncherApplication app = (LauncherApplication) context.getApplicationContext(); - item.id = app.getLauncherProvider().generateNewId(); - values.put(LauncherSettings.Favorites._ID, item.id); - item.updateValuesWithCoordinates(values, item.cellX, item.cellY); - - Runnable r = new Runnable() { - public void run() { - cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : - LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); - - if (sItemsIdMap.containsKey(item.id)) { - // we should not be adding new items in the db with the same id - throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " + - "addItemToDatabase already exists." + item.toString()); - } - sItemsIdMap.put(item.id, item); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - sFolders.put(item.id, (FolderInfo) item); - // Fall through - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || - item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - sWorkspaceItems.add(item); - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - sAppWidgets.add((LauncherAppWidgetInfo) item); - break; - } - } - }; - - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } - } - - /** - * Creates a new unique child id, for a given cell span across all layouts. - */ - static int getCellLayoutChildId( - long container, int screen, int localCellX, int localCellY, int spanX, int spanY) { - return (((int) container & 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; - } - - /** - * Removes the specified item from the database - * @param context - * @param item - */ - static void deleteItemFromDatabase(Context context, final ItemInfo item) { - final ContentResolver cr = context.getContentResolver(); - final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false); - Runnable r = new Runnable() { - public void run() { - cr.delete(uriToDelete, null, null); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - sFolders.remove(item.id); - sWorkspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - sWorkspaceItems.remove(item); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - sAppWidgets.remove((LauncherAppWidgetInfo) item); - break; - } - sItemsIdMap.remove(item.id); - sDbIconCache.remove(item); - } - }; - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } - } - - /** - * Remove the contents of the specified folder from the database - */ - static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) { - final ContentResolver cr = context.getContentResolver(); - - Runnable r = new Runnable() { - public void run() { - cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); - sItemsIdMap.remove(info.id); - sFolders.remove(info.id); - sDbIconCache.remove(info); - sWorkspaceItems.remove(info); - - cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, - LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); - for (ItemInfo childInfo : info.contents) { - sItemsIdMap.remove(childInfo.id); - sDbIconCache.remove(childInfo); - } - } - }; - if (sWorkerThread.getThreadId() == Process.myTid()) { - r.run(); - } else { - sWorker.post(r); - } - } - - /** - * Set this as the current Launcher activity object for the loader. - */ - public void initialize(Callbacks callbacks) { - synchronized (mLock) { - mCallbacks = new WeakReference(callbacks); - } - } - - /** - * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and - * ACTION_PACKAGE_CHANGED. - */ - @Override - 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) - || Intent.ACTION_PACKAGE_REMOVED.equals(action) - || Intent.ACTION_PACKAGE_ADDED.equals(action)) { - final String packageName = intent.getData().getSchemeSpecificPart(); - final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - - int op = PackageUpdatedTask.OP_NONE; - - if (packageName == null || packageName.length() == 0) { - // they sent us a bad intent - return; - } - - if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - op = PackageUpdatedTask.OP_UPDATE; - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - if (!replacing) { - op = PackageUpdatedTask.OP_REMOVE; - } - // else, we are replacing the package, so a PACKAGE_ADDED will be sent - // later, we will update the package at this time - } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { - if (!replacing) { - op = PackageUpdatedTask.OP_ADD; - } else { - op = PackageUpdatedTask.OP_UPDATE; - } - } - - if (op != PackageUpdatedTask.OP_NONE) { - enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName })); - } - - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - // First, schedule to add these apps back in. - String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); - // Then, rebind everything. - 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/workspace. - forceReload(); - } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - // Check if configuration change was an mcc/mnc change which would affect app resources - // and we would need to clear out the labels in all apps/workspace. Same handling as - // above for ACTION_LOCALE_CHANGED - Configuration currentConfig = context.getResources().getConfiguration(); - if (mPreviousConfigMcc != currentConfig.mcc) { - Log.d(TAG, "Reload apps on config change. curr_mcc:" - + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc); - forceReload(); - } - // Update previousConfig - mPreviousConfigMcc = currentConfig.mcc; - } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || - SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) { - if (mCallbacks != null) { - Callbacks callbacks = mCallbacks.get(); - if (callbacks != null) { - callbacks.bindSearchablesChanged(); - } - } - } - } - - private void forceReload() { - resetLoadedState(true, true); - - // 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. - startLoaderFromBackground(); - } - - public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) { - synchronized (mLock) { - // Stop any existing loaders first, so they don't set mAllAppsLoaded or - // mWorkspaceLoaded to true later - stopLoaderLocked(); - if (resetAllAppsLoaded) mAllAppsLoaded = false; - if (resetWorkspaceLoaded) mWorkspaceLoaded = false; - } - } - - /** - * 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(false); - } - } - - // If there is already a loader task running, tell it to stop. - // returns true if isLaunching() was true on the old task - private boolean stopLoaderLocked() { - boolean isLaunching = false; - LoaderTask oldTask = mLoaderTask; - if (oldTask != null) { - if (oldTask.isLaunching()) { - isLaunching = true; - } - oldTask.stopLocked(); - } - return isLaunching; - } - - public void startLoader(boolean isLaunching) { - synchronized (mLock) { - if (DEBUG_LOADERS) { - Log.d(TAG, "startLoader isLaunching=" + isLaunching); - } - - // Don't bother to start the thread if we know it's not going to do anything - if (mCallbacks != null && mCallbacks.get() != null) { - // If there is already one running, tell it to stop. - // also, don't downgrade isLaunching if we're already running - isLaunching = isLaunching || stopLoaderLocked(); - mLoaderTask = new LoaderTask(mApp, isLaunching); - sWorkerThread.setPriority(Thread.NORM_PRIORITY); - sWorker.post(mLoaderTask); - } - } - } - - public void stopLoader() { - synchronized (mLock) { - if (mLoaderTask != null) { - mLoaderTask.stopLocked(); - } - } - } - - public boolean isAllAppsLoaded() { - return mAllAppsLoaded; - } - - boolean isLoadingWorkspace() { - synchronized (mLock) { - if (mLoaderTask != null) { - return mLoaderTask.isLoadingWorkspace(); - } - } - return false; - } - - /** - * Runnable for the thread that loads the contents of the launcher: - * - workspace icons - * - widgets - * - all apps icons - */ - private class LoaderTask implements Runnable { - private Context mContext; - private Thread mWaitThread; - private boolean mIsLaunching; - private boolean mIsLoadingAndBindingWorkspace; - private boolean mStopped; - private boolean mLoadAndBindStepFinished; - private HashMap mLabelCache; - - LoaderTask(Context context, boolean isLaunching) { - mContext = context; - mIsLaunching = isLaunching; - mLabelCache = new HashMap(); - } - - boolean isLaunching() { - return mIsLaunching; - } - - boolean isLoadingWorkspace() { - return mIsLoadingAndBindingWorkspace; - } - - private void loadAndBindWorkspace() { - mIsLoadingAndBindingWorkspace = true; - - // Load the workspace - if (DEBUG_LOADERS) { - Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); - } - - if (!mWorkspaceLoaded) { - loadWorkspace(); - synchronized (LoaderTask.this) { - if (mStopped) { - return; - } - mWorkspaceLoaded = true; - } - } - - // Bind the workspace - bindWorkspace(); - } - - private void waitForIdle() { - // Wait until the either we're stopped or the other threads are done. - // This way we don't start loading all apps until the workspace has settled - // down. - synchronized (LoaderTask.this) { - final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - mHandler.postIdle(new Runnable() { - public void run() { - synchronized (LoaderTask.this) { - mLoadAndBindStepFinished = true; - if (DEBUG_LOADERS) { - Log.d(TAG, "done with previous binding step"); - } - LoaderTask.this.notify(); - } - } - }); - - while (!mStopped && !mLoadAndBindStepFinished) { - try { - this.wait(); - } catch (InterruptedException ex) { - // Ignore - } - } - if (DEBUG_LOADERS) { - Log.d(TAG, "waited " - + (SystemClock.uptimeMillis()-workspaceWaitTime) - + "ms for previous step to finish binding"); - } - } - } - - public void run() { - // Optimize for end-user experience: if the Launcher is up and // running with the - // All Apps interface in the foreground, load All Apps first. Otherwise, load the - // workspace first (default). - final Callbacks cbk = mCallbacks.get(); - final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true; - - keep_running: { - // Elevate priority when Home launches for the first time to avoid - // starving at boot time. Staring at a blank home is not cool. - synchronized (mLock) { - if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + - (mIsLaunching ? "DEFAULT" : "BACKGROUND")); - 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(); - } else { - if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); - loadAndBindAllApps(); - } - - if (mStopped) { - break keep_running; - } - - // Whew! Hard work done. Slow us down, and wait until the UI thread has - // settled down. - synchronized (mLock) { - if (mIsLaunching) { - if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); - android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - } - } - waitForIdle(); - - // second step - if (loadWorkspaceFirst) { - if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); - loadAndBindAllApps(); - } else { - if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace"); - loadAndBindWorkspace(); - } - - // Restore the default thread priority after we are done loading items - synchronized (mLock) { - android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); - } - } - - - // Update the saved icons if necessary - if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); - for (Object key : sDbIconCache.keySet()) { - updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key)); - } - sDbIconCache.clear(); - - // Clear out this reference, otherwise we end up holding it until all of the - // callback runnables are done. - mContext = null; - - synchronized (mLock) { - // If we are still the last one to be scheduled, remove ourselves. - if (mLoaderTask == this) { - mLoaderTask = null; - } - } - } - - public void stopLocked() { - synchronized (LoaderTask.this) { - mStopped = true; - this.notify(); - } - } - - /** - * Gets the callbacks object. If we've been stopped, or if the launcher object - * has somehow been garbage collected, return null instead. Pass in the Callbacks - * object that was around when the deferred message was scheduled, and if there's - * a new Callbacks object around then also return null. This will save us from - * calling onto it with data that will be ignored. - */ - Callbacks tryGetCallbacks(Callbacks oldCallbacks) { - synchronized (mLock) { - if (mStopped) { - return null; - } - - if (mCallbacks == null) { - return null; - } - - final Callbacks callbacks = mCallbacks.get(); - if (callbacks != oldCallbacks) { - return null; - } - if (callbacks == null) { - Log.w(TAG, "no mCallbacks"); - return null; - } - - return callbacks; - } - } - - // check & update map of what's occupied; used to discard overlapping/invalid items - private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) { - int containerIndex = item.screen; - if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - // Return early if we detect that an item is under the hotseat button - if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) { - return false; - } - - // We use the last index to refer to the hotseat and the screen as the rank, so - // test and update the occupied state accordingly - if (occupied[Launcher.SCREEN_COUNT][item.screen][0] != null) { - Log.e(TAG, "Error loading shortcut into hotseat " + item - + " into position (" + item.screen + ":" + item.cellX + "," + item.cellY - + ") occupied by " + occupied[Launcher.SCREEN_COUNT][item.screen][0]); - return false; - } else { - occupied[Launcher.SCREEN_COUNT][item.screen][0] = item; - return true; - } - } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { - // Skip further checking if it is not the hotseat or workspace container - return true; - } - - // Check if any workspace icons overlap with each other - for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { - for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { - if (occupied[containerIndex][x][y] != null) { - Log.e(TAG, "Error loading shortcut " + item - + " into cell (" + containerIndex + "-" + item.screen + ":" - + x + "," + y - + ") occupied by " - + occupied[containerIndex][x][y]); - return false; - } - } - } - for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { - for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { - occupied[containerIndex][x][y] = item; - } - } - - return true; - } - - private void loadWorkspace() { - final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - final Context context = mContext; - final ContentResolver contentResolver = context.getContentResolver(); - final PackageManager manager = context.getPackageManager(); - final AppWidgetManager widgets = AppWidgetManager.getInstance(context); - final boolean isSafeMode = manager.isSafeMode(); - - // Make sure the default workspace is loaded, if needed - mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(); - - sWorkspaceItems.clear(); - sAppWidgets.clear(); - sFolders.clear(); - sItemsIdMap.clear(); - sDbIconCache.clear(); - - final ArrayList itemsToRemove = new ArrayList(); - - final Cursor c = contentResolver.query( - LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); - - // +1 for the hotseat (it can be larger than the workspace) - // Load workspace in reverse order to ensure that latest items are loaded first (and - // before any earlier duplicates) - final ItemInfo occupied[][][] = - new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; - - try { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); - final int intentIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.INTENT); - final int titleIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.TITLE); - final int iconTypeIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ICON_TYPE); - final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); - final int iconPackageIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ICON_PACKAGE); - final int iconResourceIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ICON_RESOURCE); - final int containerIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.CONTAINER); - final int itemTypeIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ITEM_TYPE); - final int appWidgetIdIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.APPWIDGET_ID); - 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); - //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); - //final int displayModeIndex = c.getColumnIndexOrThrow( - // LauncherSettings.Favorites.DISPLAY_MODE); - - ShortcutInfo info; - String intentDescription; - LauncherAppWidgetInfo appWidgetInfo; - int container; - long id; - Intent intent; - - while (!mStopped && c.moveToNext()) { - try { - int itemType = c.getInt(itemTypeIndex); - - switch (itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - intentDescription = c.getString(intentIndex); - try { - intent = Intent.parseUri(intentDescription, 0); - } catch (URISyntaxException e) { - continue; - } - - if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - info = getShortcutInfo(manager, intent, context, c, iconIndex, - titleIndex, mLabelCache); - } else { - info = getShortcutInfo(c, context, iconTypeIndex, - iconPackageIndex, iconResourceIndex, iconIndex, - titleIndex); - - // App shortcuts that used to be automatically added to Launcher - // didn't always have the correct intent flags set, so do that here - if (intent.getAction() != null && - intent.getCategories() != null && - intent.getAction().equals(Intent.ACTION_MAIN) && - intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { - intent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - } - } - - if (info != null) { - info.intent = intent; - info.id = c.getLong(idIndex); - container = c.getInt(containerIndex); - info.container = container; - info.screen = c.getInt(screenIndex); - info.cellX = c.getInt(cellXIndex); - info.cellY = c.getInt(cellYIndex); - - // check & update map of what's occupied - if (!checkItemPlacement(occupied, info)) { - break; - } - - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - sWorkspaceItems.add(info); - break; - default: - // Item is in a user folder - FolderInfo folderInfo = - findOrMakeFolder(sFolders, container); - folderInfo.add(info); - break; - } - sItemsIdMap.put(info.id, info); - - // now that we've loaded everthing re-save it with the - // icon in case it disappears somehow. - queueIconToBeChecked(sDbIconCache, info, c, iconIndex); - } else { - // Failed to load the shortcut, probably because the - // activity manager couldn't resolve it (maybe the app - // was uninstalled), or the db row was somehow screwed up. - // Delete it. - id = c.getLong(idIndex); - Log.e(TAG, "Error loading shortcut " + id + ", removing it"); - contentResolver.delete(LauncherSettings.Favorites.getContentUri( - id, false), null, null); - } - break; - - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - id = c.getLong(idIndex); - FolderInfo folderInfo = findOrMakeFolder(sFolders, id); - - folderInfo.title = c.getString(titleIndex); - folderInfo.id = id; - container = c.getInt(containerIndex); - folderInfo.container = container; - folderInfo.screen = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); - - // check & update map of what's occupied - if (!checkItemPlacement(occupied, folderInfo)) { - break; - } - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: - sWorkspaceItems.add(folderInfo); - break; - } - - sItemsIdMap.put(folderInfo.id, folderInfo); - sFolders.put(folderInfo.id, folderInfo); - break; - - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - // Read all Launcher-specific widget details - int appWidgetId = c.getInt(appWidgetIdIndex); - id = c.getLong(idIndex); - - final AppWidgetProviderInfo provider = - widgets.getAppWidgetInfo(appWidgetId); - - if (!isSafeMode && (provider == null || provider.provider == null || - provider.provider.getPackageName() == null)) { - String log = "Deleting widget that isn't installed anymore: id=" - + id + " appWidgetId=" + appWidgetId; - Log.e(TAG, log); - Launcher.sDumpLogs.add(log); - itemsToRemove.add(id); - } else { - appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, - provider.provider); - appWidgetInfo.id = id; - appWidgetInfo.screen = c.getInt(screenIndex); - appWidgetInfo.cellX = c.getInt(cellXIndex); - appWidgetInfo.cellY = c.getInt(cellYIndex); - appWidgetInfo.spanX = c.getInt(spanXIndex); - appWidgetInfo.spanY = c.getInt(spanYIndex); - int[] minSpan = Launcher.getMinSpanForWidget(context, provider); - appWidgetInfo.minSpanX = minSpan[0]; - appWidgetInfo.minSpanY = minSpan[1]; - - container = c.getInt(containerIndex); - if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && - container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - Log.e(TAG, "Widget found where container " - + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); - continue; - } - appWidgetInfo.container = c.getInt(containerIndex); - - // check & update map of what's occupied - if (!checkItemPlacement(occupied, appWidgetInfo)) { - break; - } - sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); - sAppWidgets.add(appWidgetInfo); - } - break; - } - } catch (Exception e) { - Log.w(TAG, "Desktop items loading interrupted:", e); - } - } - } finally { - c.close(); - } - - if (itemsToRemove.size() > 0) { - ContentProviderClient client = contentResolver.acquireContentProviderClient( - LauncherSettings.Favorites.CONTENT_URI); - // Remove dead items - for (long id : itemsToRemove) { - if (DEBUG_LOADERS) { - Log.d(TAG, "Removed id = " + id); - } - // Don't notify content observers - try { - client.delete(LauncherSettings.Favorites.getContentUri(id, false), - null, null); - } catch (RemoteException e) { - Log.w(TAG, "Could not remove id = " + id); - } - } - } - - if (DEBUG_LOADERS) { - Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); - Log.d(TAG, "workspace layout: "); - 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 < mCellCountX; x++) { - line += ((occupied[s][x][y] != null) ? "#" : "."); - } - } - Log.d(TAG, "[ " + line + " ]"); - } - } - } - - /** - * Read everything out of our database. - */ - private void bindWorkspace() { - final long t = SystemClock.uptimeMillis(); - - // Don't use these two variables in any of the callback runnables. - // Otherwise we hold a reference to them. - final Callbacks oldCallbacks = mCallbacks.get(); - if (oldCallbacks == null) { - // This launcher has exited and nobody bothered to tell us. Just bail. - Log.w(TAG, "LoaderTask running with no launcher"); - return; - } - - // Get the list of workspace items to load and unbind the existing ShortcutInfos - // before we call startBinding() below. - final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); - final ArrayList tmpWorkspaceItems = unbindWorkspaceItemsOnMainThread(); - // Order the items for loading as follows: current workspace, hotseat, everything else - Collections.sort(tmpWorkspaceItems, new Comparator() { - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - int cellCountX = LauncherModel.getCellCountX(); - int cellCountY = LauncherModel.getCellCountY(); - int screenOffset = cellCountX * cellCountY; - int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat - long lr = (lhs.container * containerOffset + lhs.screen * screenOffset + - lhs.cellY * cellCountX + lhs.cellX); - long rr = (rhs.container * containerOffset + rhs.screen * screenOffset + - rhs.cellY * cellCountX + rhs.cellX); - return (int) (lr - rr); - } - }); - // Precondition: the items are ordered by page, screen - final ArrayList workspaceItems = new ArrayList(); - for (ItemInfo ii : tmpWorkspaceItems) { - // Prepend the current items, hotseat items, append everything else - if (ii.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && - ii.screen == currentScreen) { - workspaceItems.add(0, ii); - } else if (ii.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - workspaceItems.add(0, ii); - } else { - workspaceItems.add(ii); - } - } - - // Tell the workspace that we're about to start firing items at it - mHandler.post(new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.startBinding(); - } - } - }); - - // Add the items to the workspace. - int N = workspaceItems.size(); - for (int i=0; i folders = new HashMap(sFolders); - mHandler.post(new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindFolders(folders); - } - } - }); - // Wait until the queue goes empty. - mHandler.post(new Runnable() { - public void run() { - if (DEBUG_LOADERS) { - Log.d(TAG, "Going to start binding widgets soon."); - } - } - }); - // Bind the widgets, one at a time. - // WARNING: this is calling into the workspace from the background thread, - // but since getCurrentScreen() just returns the int, we should be okay. This - // is just a hint for the order, and if it's wrong, we'll be okay. - // TODO: instead, we should have that push the current screen into here. - N = sAppWidgets.size(); - // once for the current screen - for (int i=0; i list - = (ArrayList) mAllAppsList.data.clone(); - mHandler.post(new Runnable() { - public void run() { - final long t = SystemClock.uptimeMillis(); - final Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null) { - callbacks.bindAllApplications(list); - } - if (DEBUG_LOADERS) { - Log.d(TAG, "bound all " + list.size() + " apps from cache in " - + (SystemClock.uptimeMillis()-t) + "ms"); - } - } - }); - - } - - private void loadAllAppsByBatch() { - final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - // Don't use these two variables in any of the callback runnables. - // Otherwise we hold a reference to them. - final Callbacks oldCallbacks = mCallbacks.get(); - if (oldCallbacks == null) { - // This launcher has exited and nobody bothered to tell us. Just bail. - Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)"); - return; - } - - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - final PackageManager packageManager = mContext.getPackageManager(); - List apps = null; - - int N = Integer.MAX_VALUE; - - int startIndex; - int i=0; - int batchSize = -1; - while (i < N && !mStopped) { - if (i == 0) { - mAllAppsList.clear(); - final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - apps = packageManager.queryIntentActivities(mainIntent, 0); - if (DEBUG_LOADERS) { - Log.d(TAG, "queryIntentActivities took " - + (SystemClock.uptimeMillis()-qiaTime) + "ms"); - } - if (apps == null) { - return; - } - N = apps.size(); - if (DEBUG_LOADERS) { - Log.d(TAG, "queryIntentActivities got " + N + " apps"); - } - if (N == 0) { - // There are no apps?!? - return; - } - if (mBatchSize == 0) { - batchSize = N; - } else { - batchSize = mBatchSize; - } - - final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - Collections.sort(apps, - new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache)); - if (DEBUG_LOADERS) { - Log.d(TAG, "sort took " - + (SystemClock.uptimeMillis()-sortTime) + "ms"); - } - } - - final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - startIndex = i; - for (int j=0; i added = mAllAppsList.added; - mAllAppsList.added = new ArrayList(); - - mHandler.post(new Runnable() { - public void run() { - final long t = SystemClock.uptimeMillis(); - if (callbacks != null) { - if (first) { - callbacks.bindAllApplications(added); - } else { - callbacks.bindAppsAdded(added); - } - if (DEBUG_LOADERS) { - Log.d(TAG, "bound " + added.size() + " apps in " - + (SystemClock.uptimeMillis() - t) + "ms"); - } - } else { - Log.i(TAG, "not binding apps: no Launcher activity"); - } - } - }); - - if (DEBUG_LOADERS) { - Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in " - + (SystemClock.uptimeMillis()-t2) + "ms"); - } - - if (mAllAppsLoadDelay > 0 && i < N) { - try { - if (DEBUG_LOADERS) { - Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); - } - Thread.sleep(mAllAppsLoadDelay); - } catch (InterruptedException exc) { } - } - } - - if (DEBUG_LOADERS) { - Log.d(TAG, "cached all " + N + " apps in " - + (SystemClock.uptimeMillis()-t) + "ms" - + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); - } - } - - public void dumpState() { - Log.d(TAG, "mLoaderTask.mContext=" + mContext); - Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread); - Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); - Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); - Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); - Log.d(TAG, "mItems size=" + sWorkspaceItems.size()); - } - } - - void enqueuePackageUpdated(PackageUpdatedTask task) { - sWorker.post(task); - } - - private class PackageUpdatedTask implements Runnable { - int mOp; - String[] mPackages; - - public static final int OP_NONE = 0; - public static final int OP_ADD = 1; - public static final int OP_UPDATE = 2; - public static final int OP_REMOVE = 3; // uninstlled - public static final int OP_UNAVAILABLE = 4; // external media unmounted - - - public PackageUpdatedTask(int op, String[] packages) { - mOp = op; - mPackages = packages; - } - - public void run() { - final Context context = mApp; - - final String[] packages = mPackages; - final int N = packages.length; - switch (mOp) { - case OP_ADD: - for (int i=0; i added = null; - ArrayList removed = null; - ArrayList modified = null; - - if (mAllAppsList.added.size() > 0) { - added = mAllAppsList.added; - mAllAppsList.added = new ArrayList(); - } - if (mAllAppsList.removed.size() > 0) { - removed = mAllAppsList.removed; - mAllAppsList.removed = new ArrayList(); - for (ApplicationInfo info: removed) { - mIconCache.remove(info.intent.getComponent()); - } - } - if (mAllAppsList.modified.size() > 0) { - modified = mAllAppsList.modified; - mAllAppsList.modified = new ArrayList(); - } - - final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; - if (callbacks == null) { - Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); - return; - } - - if (added != null) { - final ArrayList addedFinal = added; - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; - if (callbacks == cb && cb != null) { - callbacks.bindAppsAdded(addedFinal); - } - } - }); - } - if (modified != null) { - final ArrayList modifiedFinal = modified; - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; - if (callbacks == cb && cb != null) { - callbacks.bindAppsUpdated(modifiedFinal); - } - } - }); - } - if (removed != null) { - final boolean permanent = mOp != OP_UNAVAILABLE; - final ArrayList removedFinal = removed; - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; - if (callbacks == cb && cb != null) { - callbacks.bindAppsRemoved(removedFinal, permanent); - } - } - }); - } - - mHandler.post(new Runnable() { - @Override - public void run() { - Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; - if (callbacks == cb && cb != null) { - callbacks.bindPackagesUpdated(); - } - } - }); - } - } - - /** - * Returns all the Workspace ShortcutInfos associated with a particular package. - * @param intent - * @return - */ - ArrayList getShortcutInfosForPackage(String packageName) { - ArrayList infos = new ArrayList(); - for (ItemInfo i : sWorkspaceItems) { - if (i instanceof ShortcutInfo) { - ShortcutInfo info = (ShortcutInfo) i; - if (packageName.equals(info.getPackageName())) { - infos.add(info); - } - } - } - return infos; - } - - /** - * This is called from the code that adds shortcuts from the intent receiver. This - * doesn't have a Cursor, but - */ - public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { - return getShortcutInfo(manager, intent, context, null, -1, -1, null); - } - - /** - * Make an ShortcutInfo object for a shortcut that is an application. - * - * If c is not null, then it will be used to fill in missing data like the title and icon. - */ - public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, - Cursor c, int iconIndex, int titleIndex, HashMap labelCache) { - Bitmap icon = null; - final ShortcutInfo info = new ShortcutInfo(); - - ComponentName componentName = intent.getComponent(); - if (componentName == null) { - return null; - } - - try { - PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0); - if (!pi.applicationInfo.enabled) { - // If we return null here, the corresponding item will be removed from the launcher - // db and will not appear in the workspace. - return null; - } - } catch (NameNotFoundException e) { - Log.d(TAG, "getPackInfo failed for package " + componentName.getPackageName()); - } - - // TODO: See if the PackageManager knows about this case. If it doesn't - // then return null & delete this. - - // the resource -- This may implicitly give us back the fallback icon, - // but don't worry about that. All we're doing with usingFallbackIcon is - // to avoid saving lots of copies of that in the database, and most apps - // have icons anyway. - - // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and - // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info - // via resolveActivity(). - ResolveInfo resolveInfo = null; - ComponentName oldComponent = intent.getComponent(); - Intent newIntent = new Intent(intent.getAction(), null); - newIntent.addCategory(Intent.CATEGORY_LAUNCHER); - newIntent.setPackage(oldComponent.getPackageName()); - List infos = manager.queryIntentActivities(newIntent, 0); - for (ResolveInfo i : infos) { - ComponentName cn = new ComponentName(i.activityInfo.packageName, - i.activityInfo.name); - if (cn.equals(oldComponent)) { - resolveInfo = i; - } - } - if (resolveInfo == null) { - resolveInfo = manager.resolveActivity(intent, 0); - } - if (resolveInfo != null) { - icon = mIconCache.getIcon(componentName, resolveInfo, labelCache); - } - // the db - if (icon == null) { - if (c != null) { - icon = getIconFromCursor(c, iconIndex, context); - } - } - // the fallback icon - if (icon == null) { - icon = getFallbackIcon(); - info.usingFallbackIcon = true; - } - info.setIcon(icon); - - // from the resource - if (resolveInfo != null) { - ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo); - if (labelCache != null && labelCache.containsKey(key)) { - info.title = labelCache.get(key); - } else { - info.title = resolveInfo.activityInfo.loadLabel(manager); - if (labelCache != null) { - labelCache.put(key, info.title); - } - } - } - // from the db - if (info.title == null) { - if (c != null) { - info.title = c.getString(titleIndex); - } - } - // fall back to the class name of the activity - if (info.title == null) { - info.title = componentName.getClassName(); - } - info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - return info; - } - - /** - * Make an ShortcutInfo object for a shortcut that isn't an application. - */ - private ShortcutInfo getShortcutInfo(Cursor c, Context context, - int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, - int titleIndex) { - - Bitmap icon = null; - final ShortcutInfo info = new ShortcutInfo(); - info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; - - // TODO: If there's an explicit component and we can't install that, delete it. - - info.title = c.getString(titleIndex); - - int iconType = c.getInt(iconTypeIndex); - switch (iconType) { - case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: - String packageName = c.getString(iconPackageIndex); - String resourceName = c.getString(iconResourceIndex); - PackageManager packageManager = context.getPackageManager(); - info.customIcon = false; - // the resource - try { - Resources resources = packageManager.getResourcesForApplication(packageName); - if (resources != null) { - final int id = resources.getIdentifier(resourceName, null, null); - icon = Utilities.createIconBitmap( - mIconCache.getFullResIcon(resources, id), context); - } - } catch (Exception e) { - // drop this. we have other places to look for icons - } - // the db - if (icon == null) { - icon = getIconFromCursor(c, iconIndex, context); - } - // the fallback icon - if (icon == null) { - icon = getFallbackIcon(); - info.usingFallbackIcon = true; - } - break; - case LauncherSettings.Favorites.ICON_TYPE_BITMAP: - icon = getIconFromCursor(c, iconIndex, context); - if (icon == null) { - icon = getFallbackIcon(); - info.customIcon = false; - info.usingFallbackIcon = true; - } else { - info.customIcon = true; - } - break; - default: - icon = getFallbackIcon(); - info.usingFallbackIcon = true; - info.customIcon = false; - break; - } - info.setIcon(icon); - return info; - } - - Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - Log.d(TAG, "getIconFromCursor app=" - + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); - } - byte[] data = c.getBlob(iconIndex); - try { - return Utilities.createIconBitmap( - BitmapFactory.decodeByteArray(data, 0, data.length), context); - } catch (Exception e) { - return null; - } - } - - ShortcutInfo addShortcut(Context context, Intent data, long container, int screen, - int cellX, int cellY, boolean notify) { - final ShortcutInfo info = infoFromShortcutIntent(context, data, null); - if (info == null) { - return null; - } - addItemToDatabase(context, info, container, screen, cellX, cellY, notify); - - return info; - } - - /** - * Attempts to find an AppWidgetProviderInfo that matches the given component. - */ - AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, - ComponentName component) { - List 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 resolveWidgetsForMimeType(Context context, String mimeType) { - final PackageManager packageManager = context.getPackageManager(); - final List supportedConfigurationActivities = - new ArrayList(); - - 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 widgets = - AppWidgetManager.getInstance(context).getInstalledProviders(); - final HashMap configurationComponentToWidget = - new HashMap(); - 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 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); - - if (intent == null) { - // If the intent is null, we can't construct a valid ShortcutInfo, so we return null - Log.e(TAG, "Can't construct ShorcutInfo with null intent"); - return null; - } - - Bitmap icon = null; - boolean customIcon = false; - ShortcutIconResource iconResource = null; - - if (bitmap != null && bitmap instanceof Bitmap) { - icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context); - customIcon = true; - } else { - Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); - if (extra != null && extra instanceof ShortcutIconResource) { - try { - iconResource = (ShortcutIconResource) extra; - final PackageManager packageManager = context.getPackageManager(); - Resources resources = packageManager.getResourcesForApplication( - iconResource.packageName); - final int id = resources.getIdentifier(iconResource.resourceName, null, null); - icon = Utilities.createIconBitmap( - mIconCache.getFullResIcon(resources, id), context); - } catch (Exception e) { - Log.w(TAG, "Could not load shortcut icon: " + extra); - } - } - } - - final ShortcutInfo info = new ShortcutInfo(); - - if (icon == null) { - if (fallbackIcon != null) { - icon = fallbackIcon; - } else { - icon = getFallbackIcon(); - info.usingFallbackIcon = true; - } - } - info.setIcon(icon); - - info.title = name; - info.intent = intent; - info.customIcon = customIcon; - info.iconResource = iconResource; - - return info; - } - - boolean queueIconToBeChecked(HashMap cache, ShortcutInfo info, Cursor c, - int iconIndex) { - // If apps can't be on SD, don't even bother. - if (!mAppsCanBeOnExternalStorage) { - return false; - } - // If this icon doesn't have a custom icon, check to see - // what's stored in the DB, and if it doesn't match what - // we're going to show, store what we are going to show back - // into the DB. We do this so when we're loading, if the - // package manager can't find an icon (for example because - // the app is on SD) then we can use that instead. - if (!info.customIcon && !info.usingFallbackIcon) { - cache.put(info, c.getBlob(iconIndex)); - return true; - } - return false; - } - void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) { - boolean needSave = false; - try { - if (data != null) { - Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); - Bitmap loaded = info.getIcon(mIconCache); - needSave = !saved.sameAs(loaded); - } else { - needSave = true; - } - } catch (Exception e) { - needSave = true; - } - if (needSave) { - Log.d(TAG, "going to save icon bitmap for info=" + info); - // This is slower than is ideal, but this only happens once - // or when the app is updated with a new icon. - updateItemInDatabase(context, info); - } - } - - /** - * Return an existing FolderInfo object if we have encountered this ID previously, - * or make a new one. - */ - private static FolderInfo findOrMakeFolder(HashMap folders, long id) { - // See if a placeholder was created for us already - FolderInfo folderInfo = folders.get(id); - if (folderInfo == null) { - // No placeholder -- create a new instance - folderInfo = new FolderInfo(); - folders.put(id, folderInfo); - } - return folderInfo; - } - - private static final Collator sCollator = Collator.getInstance(); - public static final Comparator APP_NAME_COMPARATOR - = new Comparator() { - public final int compare(ApplicationInfo a, ApplicationInfo b) { - int result = sCollator.compare(a.title.toString(), b.title.toString()); - if (result == 0) { - result = a.componentName.compareTo(b.componentName); - } - return result; - } - }; - public static final Comparator APP_INSTALL_TIME_COMPARATOR - = new Comparator() { - public final int compare(ApplicationInfo a, ApplicationInfo b) { - if (a.firstInstallTime < b.firstInstallTime) return 1; - if (a.firstInstallTime > b.firstInstallTime) return -1; - return 0; - } - }; - public static final Comparator WIDGET_NAME_COMPARATOR - = new Comparator() { - public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) { - return sCollator.compare(a.label.toString(), b.label.toString()); - } - }; - static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) { - if (info.activityInfo != null) { - return new ComponentName(info.activityInfo.packageName, info.activityInfo.name); - } else { - return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); - } - } - public static class ShortcutNameComparator implements Comparator { - private PackageManager mPackageManager; - private HashMap mLabelCache; - ShortcutNameComparator(PackageManager pm) { - mPackageManager = pm; - mLabelCache = new HashMap(); - } - ShortcutNameComparator(PackageManager pm, HashMap labelCache) { - mPackageManager = pm; - mLabelCache = labelCache; - } - public final int compare(ResolveInfo a, ResolveInfo b) { - CharSequence labelA, labelB; - ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a); - ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b); - if (mLabelCache.containsKey(keyA)) { - labelA = mLabelCache.get(keyA); - } else { - labelA = a.loadLabel(mPackageManager).toString(); - - mLabelCache.put(keyA, labelA); - } - if (mLabelCache.containsKey(keyB)) { - labelB = mLabelCache.get(keyB); - } else { - labelB = b.loadLabel(mPackageManager).toString(); - - mLabelCache.put(keyB, labelB); - } - return sCollator.compare(labelA, labelB); - } - }; - public static class WidgetAndShortcutNameComparator implements Comparator { - private PackageManager mPackageManager; - private HashMap mLabelCache; - WidgetAndShortcutNameComparator(PackageManager pm) { - mPackageManager = pm; - mLabelCache = new HashMap(); - } - public final int compare(Object a, Object b) { - String labelA, labelB; - if (mLabelCache.containsKey(a)) { - labelA = mLabelCache.get(a); - } else { - labelA = (a instanceof AppWidgetProviderInfo) ? - ((AppWidgetProviderInfo) a).label : - ((ResolveInfo) a).loadLabel(mPackageManager).toString(); - mLabelCache.put(a, labelA); - } - if (mLabelCache.containsKey(b)) { - labelB = mLabelCache.get(b); - } else { - labelB = (b instanceof AppWidgetProviderInfo) ? - ((AppWidgetProviderInfo) b).label : - ((ResolveInfo) b).loadLabel(mPackageManager).toString(); - mLabelCache.put(b, labelB); - } - return sCollator.compare(labelA, labelB); - } - }; - - public void dumpState() { - Log.d(TAG, "mCallbacks=" + mCallbacks); - ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); - ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); - ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); - ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); - if (mLoaderTask != null) { - mLoaderTask.dumpState(); - } else { - Log.d(TAG, "mLoaderTask=null"); - } - } -} diff --git a/src/com/android/launcher2/LauncherProvider.java b/src/com/android/launcher2/LauncherProvider.java deleted file mode 100644 index 0720259f4..000000000 --- a/src/com/android/launcher2/LauncherProvider.java +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.app.SearchManager; -import android.appwidget.AppWidgetHost; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.database.sqlite.SQLiteStatement; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.util.Xml; - -import com.android.launcher.R; -import com.android.launcher2.LauncherSettings.Favorites; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; - -public class LauncherProvider extends ContentProvider { - private static final String TAG = "Launcher.LauncherProvider"; - private static final boolean LOGD = false; - - private static final String DATABASE_NAME = "launcher.db"; - - private static final int DATABASE_VERSION = 12; - - static final String AUTHORITY = "com.android.launcher2.settings"; - - static final String TABLE_FAVORITES = "favorites"; - static final String PARAMETER_NOTIFY = "notify"; - static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED = - "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED"; - - private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = - "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; - - /** - * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when - * {@link AppWidgetHost#deleteHost()} is called during database creation. - * Use this to recall {@link AppWidgetHost#startListening()} if needed. - */ - static final Uri CONTENT_APPWIDGET_RESET_URI = - Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); - - private DatabaseHelper mOpenHelper; - - @Override - public boolean onCreate() { - mOpenHelper = new DatabaseHelper(getContext()); - ((LauncherApplication) getContext()).setLauncherProvider(this); - return true; - } - - @Override - public String getType(Uri uri) { - SqlArguments args = new SqlArguments(uri, null, null); - if (TextUtils.isEmpty(args.where)) { - return "vnd.android.cursor.dir/" + args.table; - } else { - return "vnd.android.cursor.item/" + args.table; - } - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - - SqlArguments args = new SqlArguments(uri, selection, selectionArgs); - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(args.table); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); - result.setNotificationUri(getContext().getContentResolver(), uri); - - return result; - } - - private static long dbInsertAndCheck(DatabaseHelper helper, - SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { - if (!values.containsKey(LauncherSettings.Favorites._ID)) { - throw new RuntimeException("Error: attempting to add item without specifying an id"); - } - return db.insert(table, nullColumnHack, values); - } - - private static void deleteId(SQLiteDatabase db, long id) { - Uri uri = LauncherSettings.Favorites.getContentUri(id, false); - SqlArguments args = new SqlArguments(uri, null, null); - db.delete(args.table, args.where, args.args); - } - - @Override - public Uri insert(Uri uri, ContentValues initialValues) { - SqlArguments args = new SqlArguments(uri); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); - if (rowId <= 0) return null; - - uri = ContentUris.withAppendedId(uri, rowId); - sendNotify(uri); - - return uri; - } - - @Override - public int bulkInsert(Uri uri, ContentValues[] values) { - SqlArguments args = new SqlArguments(uri); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - int numValues = values.length; - for (int i = 0; i < numValues; i++) { - if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { - return 0; - } - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - sendNotify(uri); - return values.length; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - SqlArguments args = new SqlArguments(uri, selection, selectionArgs); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count = db.delete(args.table, args.where, args.args); - if (count > 0) sendNotify(uri); - - return count; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - SqlArguments args = new SqlArguments(uri, selection, selectionArgs); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count = db.update(args.table, values, args.where, args.args); - if (count > 0) sendNotify(uri); - - return count; - } - - private void sendNotify(Uri uri) { - String notify = uri.getQueryParameter(PARAMETER_NOTIFY); - if (notify == null || "true".equals(notify)) { - getContext().getContentResolver().notifyChange(uri, null); - } - } - - public long generateNewId() { - return mOpenHelper.generateNewId(); - } - - synchronized public void loadDefaultFavoritesIfNecessary() { - String spKey = LauncherApplication.getSharedPreferencesKey(); - SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); - if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) { - // Populate favorites table with initial favorites - SharedPreferences.Editor editor = sp.edit(); - editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED); - mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace); - editor.commit(); - } - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - private static final String TAG_FAVORITES = "favorites"; - private static final String TAG_FAVORITE = "favorite"; - private static final String TAG_CLOCK = "clock"; - private static final String TAG_SEARCH = "search"; - private static final String TAG_APPWIDGET = "appwidget"; - private static final String TAG_SHORTCUT = "shortcut"; - private static final String TAG_FOLDER = "folder"; - private static final String TAG_EXTRA = "extra"; - - private final Context mContext; - private final AppWidgetHost mAppWidgetHost; - private long mMaxId = -1; - - DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - mContext = context; - mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); - - // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from - // the DB here - if (mMaxId == -1) { - mMaxId = initializeMaxId(getWritableDatabase()); - } - } - - /** - * Send notification that we've deleted the {@link AppWidgetHost}, - * probably as part of the initial database creation. The receiver may - * want to re-call {@link AppWidgetHost#startListening()} to ensure - * callbacks are correctly set. - */ - private void sendAppWidgetResetNotify() { - final ContentResolver resolver = mContext.getContentResolver(); - resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); - } - - @Override - public void onCreate(SQLiteDatabase db) { - if (LOGD) Log.d(TAG, "creating new launcher database"); - - mMaxId = 1; - - db.execSQL("CREATE TABLE favorites (" + - "_id INTEGER PRIMARY KEY," + - "title TEXT," + - "intent TEXT," + - "container INTEGER," + - "screen INTEGER," + - "cellX INTEGER," + - "cellY INTEGER," + - "spanX INTEGER," + - "spanY INTEGER," + - "itemType INTEGER," + - "appWidgetId INTEGER NOT NULL DEFAULT -1," + - "isShortcut INTEGER," + - "iconType INTEGER," + - "iconPackage TEXT," + - "iconResource TEXT," + - "icon BLOB," + - "uri TEXT," + - "displayMode INTEGER" + - ");"); - - // Database was just created, so wipe any previous widgets - if (mAppWidgetHost != null) { - mAppWidgetHost.deleteHost(); - sendAppWidgetResetNotify(); - } - - if (!convertDatabase(db)) { - // Set a shared pref so that we know we need to load the default workspace later - setFlagToLoadDefaultWorkspaceLater(); - } - } - - private void setFlagToLoadDefaultWorkspaceLater() { - String spKey = LauncherApplication.getSharedPreferencesKey(); - SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true); - editor.commit(); - } - - private boolean convertDatabase(SQLiteDatabase db) { - if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); - boolean converted = false; - - final Uri uri = Uri.parse("content://" + Settings.AUTHORITY + - "/old_favorites?notify=true"); - final ContentResolver resolver = mContext.getContentResolver(); - Cursor cursor = null; - - try { - cursor = resolver.query(uri, null, null, null, null); - } catch (Exception e) { - // Ignore - } - - // We already have a favorites database in the old provider - if (cursor != null && cursor.getCount() > 0) { - try { - converted = copyFromCursor(db, cursor) > 0; - } finally { - cursor.close(); - } - - if (converted) { - resolver.delete(uri, null, null); - } - } - - if (converted) { - // Convert widgets from this import into widgets - if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); - convertWidgets(db); - } - - return converted; - } - - private int copyFromCursor(SQLiteDatabase db, Cursor c) { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); - final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); - final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); - final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); - final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); - final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); - 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 uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); - final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); - - ContentValues[] rows = new ContentValues[c.getCount()]; - int i = 0; - while (c.moveToNext()) { - ContentValues values = new ContentValues(c.getColumnCount()); - values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); - values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); - values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); - values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); - values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); - values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); - values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); - values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); - values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); - values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); - values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); - values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); - values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); - values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); - values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); - rows[i++] = values; - } - - db.beginTransaction(); - int total = 0; - try { - int numValues = rows.length; - for (i = 0; i < numValues; i++) { - if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { - return 0; - } else { - total++; - } - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - return total; - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (LOGD) Log.d(TAG, "onUpgrade triggered"); - - int version = oldVersion; - if (version < 3) { - // upgrade 1,2 -> 3 added appWidgetId column - db.beginTransaction(); - try { - // Insert new column for holding appWidgetIds - db.execSQL("ALTER TABLE favorites " + - "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); - db.setTransactionSuccessful(); - version = 3; - } catch (SQLException ex) { - // Old version remains, which means we wipe old data - Log.e(TAG, ex.getMessage(), ex); - } finally { - db.endTransaction(); - } - - // Convert existing widgets only if table upgrade was successful - if (version == 3) { - convertWidgets(db); - } - } - - if (version < 4) { - version = 4; - } - - // Where's version 5? - // - Donut and sholes on 2.0 shipped with version 4 of launcher1. - // - Passion shipped on 2.1 with version 6 of launcher2 - // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 - // but version 5 on there was the updateContactsShortcuts change - // which was version 6 in launcher 2 (first shipped on passion 2.1r1). - // The updateContactsShortcuts change is idempotent, so running it twice - // is okay so we'll do that when upgrading the devices that shipped with it. - if (version < 6) { - // We went from 3 to 5 screens. Move everything 1 to the right - db.beginTransaction(); - try { - db.execSQL("UPDATE favorites SET screen=(screen + 1);"); - db.setTransactionSuccessful(); - } catch (SQLException ex) { - // Old version remains, which means we wipe old data - Log.e(TAG, ex.getMessage(), ex); - } finally { - db.endTransaction(); - } - - // We added the fast track. - if (updateContactsShortcuts(db)) { - version = 6; - } - } - - if (version < 7) { - // Version 7 gets rid of the special search widget. - convertWidgets(db); - version = 7; - } - - if (version < 8) { - // Version 8 (froyo) has the icons all normalized. This should - // already be the case in practice, but we now rely on it and don't - // resample the images each time. - normalizeIcons(db); - version = 8; - } - - if (version < 9) { - // The max id is not yet set at this point (onUpgrade is triggered in the ctor - // before it gets a change to get set, so we need to read it here when we use it) - if (mMaxId == -1) { - mMaxId = initializeMaxId(db); - } - - // Add default hotseat icons - loadFavorites(db, R.xml.update_workspace); - version = 9; - } - - // We bumped the version three time during JB, once to update the launch flags, once to - // update the override for the default launch animation and once to set the mimetype - // to improve startup performance - if (version < 12) { - // Contact shortcuts need a different set of flags to be launched now - // The updateContactsShortcuts change is idempotent, so we can keep using it like - // back in the Donut days - updateContactsShortcuts(db); - version = 12; - } - - if (version != DATABASE_VERSION) { - Log.w(TAG, "Destroying all old data."); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); - onCreate(db); - } - } - - private boolean updateContactsShortcuts(SQLiteDatabase db) { - final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, - new int[] { Favorites.ITEM_TYPE_SHORTCUT }); - - Cursor c = null; - final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; - db.beginTransaction(); - try { - // Select and iterate through each matching widget - c = db.query(TABLE_FAVORITES, - new String[] { Favorites._ID, Favorites.INTENT }, - selectWhere, null, null, null, null); - if (c == null) return false; - - if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); - - final int idIndex = c.getColumnIndex(Favorites._ID); - final int intentIndex = c.getColumnIndex(Favorites.INTENT); - - while (c.moveToNext()) { - long favoriteId = c.getLong(idIndex); - final String intentUri = c.getString(intentIndex); - if (intentUri != null) { - try { - final Intent intent = Intent.parseUri(intentUri, 0); - android.util.Log.d("Home", intent.toString()); - final Uri uri = intent.getData(); - if (uri != null) { - final String data = uri.toString(); - if ((Intent.ACTION_VIEW.equals(intent.getAction()) || - actionQuickContact.equals(intent.getAction())) && - (data.startsWith("content://contacts/people/") || - data.startsWith("content://com.android.contacts/" + - "contacts/lookup/"))) { - - final Intent newIntent = new Intent(actionQuickContact); - // When starting from the launcher, start in a new, cleared task - // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we - // clear the whole thing preemptively here since - // QuickContactActivity will finish itself when launching other - // detail activities. - newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TASK); - newIntent.putExtra( - Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); - newIntent.setData(uri); - // Determine the type and also put that in the shortcut - // (that can speed up launch a bit) - newIntent.setDataAndType(uri, newIntent.resolveType(mContext)); - - final ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.INTENT, - newIntent.toUri(0)); - - String updateWhere = Favorites._ID + "=" + favoriteId; - db.update(TABLE_FAVORITES, values, updateWhere, null); - } - } - } catch (RuntimeException ex) { - Log.e(TAG, "Problem upgrading shortcut", ex); - } catch (URISyntaxException e) { - Log.e(TAG, "Problem upgrading shortcut", e); - } - } - } - - db.setTransactionSuccessful(); - } catch (SQLException ex) { - Log.w(TAG, "Problem while upgrading contacts", ex); - return false; - } finally { - db.endTransaction(); - if (c != null) { - c.close(); - } - } - - return true; - } - - private void normalizeIcons(SQLiteDatabase db) { - Log.d(TAG, "normalizing icons"); - - db.beginTransaction(); - Cursor c = null; - SQLiteStatement update = null; - try { - boolean logged = false; - update = db.compileStatement("UPDATE favorites " - + "SET icon=? WHERE _id=?"); - - c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + - Favorites.ICON_TYPE_BITMAP, null); - - final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); - final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); - - while (c.moveToNext()) { - long id = c.getLong(idIndex); - byte[] data = c.getBlob(iconIndex); - try { - Bitmap bitmap = Utilities.resampleIconBitmap( - BitmapFactory.decodeByteArray(data, 0, data.length), - mContext); - if (bitmap != null) { - update.bindLong(1, id); - data = ItemInfo.flattenBitmap(bitmap); - if (data != null) { - update.bindBlob(2, data); - update.execute(); - } - bitmap.recycle(); - } - } catch (Exception e) { - if (!logged) { - Log.e(TAG, "Failed normalizing icon " + id, e); - } else { - Log.e(TAG, "Also failed normalizing icon " + id); - } - logged = true; - } - } - db.setTransactionSuccessful(); - } catch (SQLException ex) { - Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); - } finally { - db.endTransaction(); - if (update != null) { - update.close(); - } - if (c != null) { - c.close(); - } - } - } - - // Generates a new ID to use for an object in your database. This method should be only - // called from the main UI thread. As an exception, we do call it when we call the - // constructor from the worker thread; however, this doesn't extend until after the - // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp - // after that point - public long generateNewId() { - if (mMaxId < 0) { - throw new RuntimeException("Error: max id was not initialized"); - } - mMaxId += 1; - return mMaxId; - } - - private long initializeMaxId(SQLiteDatabase db) { - Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); - - // get the result - final int maxIdIndex = 0; - long id = -1; - if (c != null && c.moveToNext()) { - id = c.getLong(maxIdIndex); - } - if (c != null) { - c.close(); - } - - if (id == -1) { - throw new RuntimeException("Error: could not query max id"); - } - - return id; - } - - /** - * Upgrade existing clock and photo frame widgets into their new widget - * equivalents. - */ - private void convertWidgets(SQLiteDatabase db) { - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); - final int[] bindSources = new int[] { - Favorites.ITEM_TYPE_WIDGET_CLOCK, - Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, - Favorites.ITEM_TYPE_WIDGET_SEARCH, - }; - - final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); - - Cursor c = null; - - db.beginTransaction(); - try { - // Select and iterate through each matching widget - c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, - selectWhere, null, null, null, null); - - if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); - - final ContentValues values = new ContentValues(); - while (c != null && c.moveToNext()) { - long favoriteId = c.getLong(0); - int favoriteType = c.getInt(1); - - // Allocate and update database with new appWidgetId - try { - int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); - - if (LOGD) { - Log.d(TAG, "allocated appWidgetId=" + appWidgetId - + " for favoriteId=" + favoriteId); - } - values.clear(); - values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); - values.put(Favorites.APPWIDGET_ID, appWidgetId); - - // Original widgets might not have valid spans when upgrading - if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { - values.put(LauncherSettings.Favorites.SPANX, 4); - values.put(LauncherSettings.Favorites.SPANY, 1); - } else { - values.put(LauncherSettings.Favorites.SPANX, 2); - values.put(LauncherSettings.Favorites.SPANY, 2); - } - - String updateWhere = Favorites._ID + "=" + favoriteId; - db.update(TABLE_FAVORITES, values, updateWhere, null); - - if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { - // TODO: check return value - appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, - new ComponentName("com.android.alarmclock", - "com.android.alarmclock.AnalogAppWidgetProvider")); - } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { - // TODO: check return value - appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, - new ComponentName("com.android.camera", - "com.android.camera.PhotoAppWidgetProvider")); - } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { - // TODO: check return value - appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, - getSearchWidgetProvider()); - } - } catch (RuntimeException ex) { - Log.e(TAG, "Problem allocating appWidgetId", ex); - } - } - - db.setTransactionSuccessful(); - } catch (SQLException ex) { - Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); - } finally { - db.endTransaction(); - if (c != null) { - c.close(); - } - } - } - - private static final void beginDocument(XmlPullParser parser, String firstElementName) - throws XmlPullParserException, IOException { - int type; - while ((type = parser.next()) != parser.START_TAG - && type != parser.END_DOCUMENT) { - ; - } - - if (type != parser.START_TAG) { - throw new XmlPullParserException("No start tag found"); - } - - if (!parser.getName().equals(firstElementName)) { - throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + - ", expected " + firstElementName); - } - } - - /** - * Loads the default set of favorite packages from an xml file. - * - * @param db The database to write the values into - * @param filterContainerId The specific container id of items to load - */ - private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { - Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - ContentValues values = new ContentValues(); - - PackageManager packageManager = mContext.getPackageManager(); - int allAppsButtonRank = - mContext.getResources().getInteger(R.integer.hotseat_all_apps_index); - int i = 0; - try { - XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId); - AttributeSet attrs = Xml.asAttributeSet(parser); - beginDocument(parser, TAG_FAVORITES); - - final int depth = parser.getDepth(); - - int type; - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - - if (type != XmlPullParser.START_TAG) { - continue; - } - - boolean added = false; - final String name = parser.getName(); - - TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); - - long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; - if (a.hasValue(R.styleable.Favorite_container)) { - container = Long.valueOf(a.getString(R.styleable.Favorite_container)); - } - - String screen = a.getString(R.styleable.Favorite_screen); - String x = a.getString(R.styleable.Favorite_x); - String y = a.getString(R.styleable.Favorite_y); - - // If we are adding to the hotseat, the screen is used as the position in the - // hotseat. This screen can't be at position 0 because AllApps is in the - // zeroth position. - if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT - && Integer.valueOf(screen) == allAppsButtonRank) { - throw new RuntimeException("Invalid screen position for hotseat item"); - } - - values.clear(); - values.put(LauncherSettings.Favorites.CONTAINER, container); - values.put(LauncherSettings.Favorites.SCREEN, screen); - values.put(LauncherSettings.Favorites.CELLX, x); - values.put(LauncherSettings.Favorites.CELLY, y); - - if (TAG_FAVORITE.equals(name)) { - long id = addAppShortcut(db, values, a, packageManager, intent); - added = id >= 0; - } else if (TAG_SEARCH.equals(name)) { - added = addSearchWidget(db, values); - } else if (TAG_CLOCK.equals(name)) { - added = addClockWidget(db, values); - } else if (TAG_APPWIDGET.equals(name)) { - added = addAppWidget(parser, attrs, type, db, values, a, packageManager); - } else if (TAG_SHORTCUT.equals(name)) { - long id = addUriShortcut(db, values, a); - added = id >= 0; - } else if (TAG_FOLDER.equals(name)) { - String title; - int titleResId = a.getResourceId(R.styleable.Favorite_title, -1); - if (titleResId != -1) { - title = mContext.getResources().getString(titleResId); - } else { - title = mContext.getResources().getString(R.string.folder_name); - } - values.put(LauncherSettings.Favorites.TITLE, title); - long folderId = addFolder(db, values); - added = folderId >= 0; - - ArrayList folderItems = new ArrayList(); - - int folderDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > folderDepth) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final String folder_item_name = parser.getName(); - - TypedArray ar = mContext.obtainStyledAttributes(attrs, - R.styleable.Favorite); - values.clear(); - values.put(LauncherSettings.Favorites.CONTAINER, folderId); - - if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) { - long id = - addAppShortcut(db, values, ar, packageManager, intent); - if (id >= 0) { - folderItems.add(id); - } - } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) { - long id = addUriShortcut(db, values, ar); - if (id >= 0) { - folderItems.add(id); - } - } else { - throw new RuntimeException("Folders can " + - "contain only shortcuts"); - } - ar.recycle(); - } - // We can only have folders with >= 2 items, so we need to remove the - // folder and clean up if less than 2 items were included, or some - // failed to add, and less than 2 were actually added - if (folderItems.size() < 2 && folderId >= 0) { - // We just delete the folder and any items that made it - deleteId(db, folderId); - if (folderItems.size() > 0) { - deleteId(db, folderItems.get(0)); - } - added = false; - } - } - if (added) i++; - a.recycle(); - } - } catch (XmlPullParserException e) { - Log.w(TAG, "Got exception parsing favorites.", e); - } catch (IOException e) { - Log.w(TAG, "Got exception parsing favorites.", e); - } catch (RuntimeException e) { - Log.w(TAG, "Got exception parsing favorites.", e); - } - - return i; - } - - private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, - PackageManager packageManager, Intent intent) { - long id = -1; - ActivityInfo info; - String packageName = a.getString(R.styleable.Favorite_packageName); - String className = a.getString(R.styleable.Favorite_className); - try { - ComponentName cn; - try { - cn = new ComponentName(packageName, className); - info = packageManager.getActivityInfo(cn, 0); - } catch (PackageManager.NameNotFoundException nnfe) { - String[] packages = packageManager.currentToCanonicalPackageNames( - new String[] { packageName }); - cn = new ComponentName(packages[0], className); - info = packageManager.getActivityInfo(cn, 0); - } - id = generateNewId(); - intent.setComponent(cn); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - values.put(Favorites.INTENT, intent.toUri(0)); - values.put(Favorites.TITLE, info.loadLabel(packageManager).toString()); - values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); - values.put(Favorites.SPANX, 1); - values.put(Favorites.SPANY, 1); - values.put(Favorites._ID, generateNewId()); - if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { - return -1; - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to add favorite: " + packageName + - "/" + className, e); - } - return id; - } - - private long addFolder(SQLiteDatabase db, ContentValues values) { - values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); - values.put(Favorites.SPANX, 1); - values.put(Favorites.SPANY, 1); - long id = generateNewId(); - values.put(Favorites._ID, id); - if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) { - return -1; - } else { - return id; - } - } - - private ComponentName getSearchWidgetProvider() { - SearchManager searchManager = - (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); - ComponentName searchComponent = searchManager.getGlobalSearchActivity(); - if (searchComponent == null) return null; - return getProviderInPackage(searchComponent.getPackageName()); - } - - /** - * Gets an appwidget provider from the given package. If the package contains more than - * one appwidget provider, an arbitrary one is returned. - */ - private ComponentName getProviderInPackage(String packageName) { - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); - List providers = appWidgetManager.getInstalledProviders(); - if (providers == null) return null; - final int providerCount = providers.size(); - for (int i = 0; i < providerCount; i++) { - ComponentName provider = providers.get(i).provider; - if (provider != null && provider.getPackageName().equals(packageName)) { - return provider; - } - } - return null; - } - - private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) { - ComponentName cn = getSearchWidgetProvider(); - return addAppWidget(db, values, cn, 4, 1, null); - } - - private boolean addClockWidget(SQLiteDatabase db, ContentValues values) { - ComponentName cn = new ComponentName("com.android.alarmclock", - "com.android.alarmclock.AnalogAppWidgetProvider"); - return addAppWidget(db, values, cn, 2, 2, null); - } - - private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type, - SQLiteDatabase db, ContentValues values, TypedArray a, - PackageManager packageManager) throws XmlPullParserException, IOException { - - String packageName = a.getString(R.styleable.Favorite_packageName); - String className = a.getString(R.styleable.Favorite_className); - - if (packageName == null || className == null) { - return false; - } - - boolean hasPackage = true; - ComponentName cn = new ComponentName(packageName, className); - try { - packageManager.getReceiverInfo(cn, 0); - } catch (Exception e) { - String[] packages = packageManager.currentToCanonicalPackageNames( - new String[] { packageName }); - cn = new ComponentName(packages[0], className); - try { - packageManager.getReceiverInfo(cn, 0); - } catch (Exception e1) { - hasPackage = false; - } - } - - if (hasPackage) { - int spanX = a.getInt(R.styleable.Favorite_spanX, 0); - int spanY = a.getInt(R.styleable.Favorite_spanY, 0); - - // Read the extras - Bundle extras = new Bundle(); - int widgetDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > widgetDepth) { - if (type != XmlPullParser.START_TAG) { - continue; - } - - TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra); - if (TAG_EXTRA.equals(parser.getName())) { - String key = ar.getString(R.styleable.Extra_key); - String value = ar.getString(R.styleable.Extra_value); - if (key != null && value != null) { - extras.putString(key, value); - } else { - throw new RuntimeException("Widget extras must have a key and value"); - } - } else { - throw new RuntimeException("Widgets can contain only extras"); - } - ar.recycle(); - } - - return addAppWidget(db, values, cn, spanX, spanY, extras); - } - - return false; - } - - private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, - int spanX, int spanY, Bundle extras) { - boolean allocatedAppWidgets = false; - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); - - try { - int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); - - values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); - values.put(Favorites.SPANX, spanX); - values.put(Favorites.SPANY, spanY); - values.put(Favorites.APPWIDGET_ID, appWidgetId); - values.put(Favorites._ID, generateNewId()); - dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); - - allocatedAppWidgets = true; - - // TODO: need to check return value - appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn); - - // Send a broadcast to configure the widget - if (extras != null && !extras.isEmpty()) { - Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); - intent.setComponent(cn); - intent.putExtras(extras); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - mContext.sendBroadcast(intent); - } - } catch (RuntimeException ex) { - Log.e(TAG, "Problem allocating appWidgetId", ex); - } - - return allocatedAppWidgets; - } - - private long addUriShortcut(SQLiteDatabase db, ContentValues values, - TypedArray a) { - Resources r = mContext.getResources(); - - final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0); - final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0); - - Intent intent; - String uri = null; - try { - uri = a.getString(R.styleable.Favorite_uri); - intent = Intent.parseUri(uri, 0); - } catch (URISyntaxException e) { - Log.w(TAG, "Shortcut has malformed uri: " + uri); - return -1; // Oh well - } - - if (iconResId == 0 || titleResId == 0) { - Log.w(TAG, "Shortcut is missing title or icon resource ID"); - return -1; - } - - long id = generateNewId(); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - values.put(Favorites.INTENT, intent.toUri(0)); - values.put(Favorites.TITLE, r.getString(titleResId)); - values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT); - values.put(Favorites.SPANX, 1); - values.put(Favorites.SPANY, 1); - values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE); - values.put(Favorites.ICON_PACKAGE, mContext.getPackageName()); - values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId)); - values.put(Favorites._ID, id); - - if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { - return -1; - } - return id; - } - } - - /** - * Build a query string that will match any row where the column matches - * anything in the values list. - */ - static String buildOrWhereString(String column, int[] values) { - StringBuilder selectWhere = new StringBuilder(); - for (int i = values.length - 1; i >= 0; i--) { - selectWhere.append(column).append("=").append(values[i]); - if (i > 0) { - selectWhere.append(" OR "); - } - } - return selectWhere.toString(); - } - - static class SqlArguments { - public final String table; - public final String where; - public final String[] args; - - SqlArguments(Uri url, String where, String[] args) { - if (url.getPathSegments().size() == 1) { - this.table = url.getPathSegments().get(0); - this.where = where; - this.args = args; - } else if (url.getPathSegments().size() != 2) { - throw new IllegalArgumentException("Invalid URI: " + url); - } else if (!TextUtils.isEmpty(where)) { - throw new UnsupportedOperationException("WHERE clause not supported: " + url); - } else { - this.table = url.getPathSegments().get(0); - this.where = "_id=" + ContentUris.parseId(url); - this.args = null; - } - } - - SqlArguments(Uri url) { - if (url.getPathSegments().size() == 1) { - table = url.getPathSegments().get(0); - where = null; - args = null; - } else { - throw new IllegalArgumentException("Invalid URI: " + url); - } - } - } -} diff --git a/src/com/android/launcher2/LauncherSettings.java b/src/com/android/launcher2/LauncherSettings.java deleted file mode 100644 index ee003716b..000000000 --- a/src/com/android/launcher2/LauncherSettings.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.net.Uri; -import android.provider.BaseColumns; - -/** - * Settings related utilities. - */ -class LauncherSettings { - static interface BaseLauncherColumns extends BaseColumns { - /** - * Descriptive name of the gesture that can be displayed to the user. - *

Type: TEXT

- */ - static final String TITLE = "title"; - - /** - * The Intent URL of the gesture, describing what it points to. This - * value is given to {@link android.content.Intent#parseUri(String, int)} to create - * an Intent that can be launched. - *

Type: TEXT

- */ - static final String INTENT = "intent"; - - /** - * The type of the gesture - * - *

Type: INTEGER

- */ - static final String ITEM_TYPE = "itemType"; - - /** - * The gesture is an application - */ - static final int ITEM_TYPE_APPLICATION = 0; - - /** - * The gesture is an application created shortcut - */ - static final int ITEM_TYPE_SHORTCUT = 1; - - /** - * The icon type. - *

Type: INTEGER

- */ - static final String ICON_TYPE = "iconType"; - - /** - * The icon is a resource identified by a package name and an integer id. - */ - static final int ICON_TYPE_RESOURCE = 0; - - /** - * The icon is a bitmap. - */ - static final int ICON_TYPE_BITMAP = 1; - - /** - * The icon package name, if icon type is ICON_TYPE_RESOURCE. - *

Type: TEXT

- */ - static final String ICON_PACKAGE = "iconPackage"; - - /** - * The icon resource id, if icon type is ICON_TYPE_RESOURCE. - *

Type: TEXT

- */ - static final String ICON_RESOURCE = "iconResource"; - - /** - * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP. - *

Type: BLOB

- */ - static final String ICON = "icon"; - } - - /** - * Favorites. - */ - static final class Favorites implements BaseLauncherColumns { - /** - * The content:// style URL for this table - */ - static final Uri CONTENT_URI = Uri.parse("content://" + - LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + - "?" + LauncherProvider.PARAMETER_NOTIFY + "=true"); - - /** - * The content:// style URL for this table. When this Uri is used, no notification is - * sent if the content changes. - */ - static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" + - LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + - "?" + LauncherProvider.PARAMETER_NOTIFY + "=false"); - - /** - * The content:// style URL for a given row, identified by its id. - * - * @param id The row id. - * @param notify True to send a notification is the content changes. - * - * @return The unique content URL for the specified row. - */ - static Uri getContentUri(long id, boolean notify) { - return Uri.parse("content://" + LauncherProvider.AUTHORITY + - "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" + - LauncherProvider.PARAMETER_NOTIFY + "=" + notify); - } - - /** - * The container holding the favorite - *

Type: INTEGER

- */ - static final String CONTAINER = "container"; - - /** - * The icon is a resource identified by a package name and an integer id. - */ - static final int CONTAINER_DESKTOP = -100; - static final int CONTAINER_HOTSEAT = -101; - - /** - * The screen holding the favorite (if container is CONTAINER_DESKTOP) - *

Type: INTEGER

- */ - static final String SCREEN = "screen"; - - /** - * The X coordinate of the cell holding the favorite - * (if container is CONTAINER_HOTSEAT or CONTAINER_HOTSEAT) - *

Type: INTEGER

- */ - static final String CELLX = "cellX"; - - /** - * The Y coordinate of the cell holding the favorite - * (if container is CONTAINER_DESKTOP) - *

Type: INTEGER

- */ - static final String CELLY = "cellY"; - - /** - * The X span of the cell holding the favorite - *

Type: INTEGER

- */ - static final String SPANX = "spanX"; - - /** - * The Y span of the cell holding the favorite - *

Type: INTEGER

- */ - static final String SPANY = "spanY"; - - /** - * The favorite is a user created folder - */ - static final int ITEM_TYPE_FOLDER = 2; - - /** - * The favorite is a live folder - * - * Note: live folders can no longer be added to Launcher, and any live folders which - * exist within the launcher database will be ignored when loading. That said, these - * entries in the database may still exist, and are not automatically stripped. - */ - static final int ITEM_TYPE_LIVE_FOLDER = 3; - - /** - * The favorite is a widget - */ - static final int ITEM_TYPE_APPWIDGET = 4; - - /** - * The favorite is a clock - */ - static final int ITEM_TYPE_WIDGET_CLOCK = 1000; - - /** - * The favorite is a search widget - */ - static final int ITEM_TYPE_WIDGET_SEARCH = 1001; - - /** - * The favorite is a photo frame - */ - static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002; - - /** - * The appWidgetId of the widget - * - *

Type: INTEGER

- */ - static final String APPWIDGET_ID = "appWidgetId"; - - /** - * Indicates whether this favorite is an application-created shortcut or not. - * If the value is 0, the favorite is not an application-created shortcut, if the - * value is 1, it is an application-created shortcut. - *

Type: INTEGER

- */ - @Deprecated - static final String IS_SHORTCUT = "isShortcut"; - - /** - * The URI associated with the favorite. It is used, for instance, by - * live folders to find the content provider. - *

Type: TEXT

- */ - static final String URI = "uri"; - - /** - * The display mode if the item is a live folder. - *

Type: INTEGER

- * - * @see android.provider.LiveFolders#DISPLAY_MODE_GRID - * @see android.provider.LiveFolders#DISPLAY_MODE_LIST - */ - static final String DISPLAY_MODE = "displayMode"; - } -} diff --git a/src/com/android/launcher2/LauncherViewPropertyAnimator.java b/src/com/android/launcher2/LauncherViewPropertyAnimator.java deleted file mode 100644 index 88b4cb4b8..000000000 --- a/src/com/android/launcher2/LauncherViewPropertyAnimator.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2012 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.Animator.AnimatorListener; -import android.animation.TimeInterpolator; -import android.view.ViewPropertyAnimator; -import android.view.View; - -import java.util.ArrayList; -import java.util.EnumSet; - -public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener { - enum Properties { - TRANSLATION_X, - TRANSLATION_Y, - SCALE_X, - SCALE_Y, - ROTATION_Y, - ALPHA, - START_DELAY, - DURATION, - INTERPOLATOR - } - EnumSet mPropertiesToSet = EnumSet.noneOf(Properties.class); - ViewPropertyAnimator mViewPropertyAnimator; - View mTarget; - - float mTranslationX; - float mTranslationY; - float mScaleX; - float mScaleY; - float mRotationY; - float mAlpha; - long mStartDelay; - long mDuration; - TimeInterpolator mInterpolator; - ArrayList mListeners; - boolean mRunning = false; - - public LauncherViewPropertyAnimator(View target) { - mTarget = target; - mListeners = new ArrayList(); - } - - @Override - public void addListener(Animator.AnimatorListener listener) { - mListeners.add(listener); - } - - @Override - public void cancel() { - if (mViewPropertyAnimator != null) { - mViewPropertyAnimator.cancel(); - } - } - - @Override - public Animator clone() { - throw new RuntimeException("Not implemented"); - } - - @Override - public void end() { - throw new RuntimeException("Not implemented"); - } - - @Override - public long getDuration() { - return mDuration; - } - - @Override - public ArrayList getListeners() { - return mListeners; - } - - @Override - public long getStartDelay() { - return mStartDelay; - } - - @Override - public void onAnimationCancel(Animator animation) { - for (int i = 0; i < mListeners.size(); i++) { - Animator.AnimatorListener listener = mListeners.get(i); - listener.onAnimationCancel(this); - } - mRunning = false; - } - - @Override - public void onAnimationEnd(Animator animation) { - for (int i = 0; i < mListeners.size(); i++) { - Animator.AnimatorListener listener = mListeners.get(i); - listener.onAnimationEnd(this); - } - mRunning = false; - } - - @Override - public void onAnimationRepeat(Animator animation) { - for (int i = 0; i < mListeners.size(); i++) { - Animator.AnimatorListener listener = mListeners.get(i); - listener.onAnimationRepeat(this); - } - } - - @Override - public void onAnimationStart(Animator animation) { - for (int i = 0; i < mListeners.size(); i++) { - Animator.AnimatorListener listener = mListeners.get(i); - listener.onAnimationStart(this); - } - mRunning = true; - } - - @Override - public boolean isRunning() { - return mRunning; - } - - @Override - public boolean isStarted() { - return mViewPropertyAnimator != null; - } - - @Override - public void removeAllListeners() { - mListeners.clear(); - } - - @Override - public void removeListener(Animator.AnimatorListener listener) { - mListeners.remove(listener); - } - - @Override - public Animator setDuration(long duration) { - mPropertiesToSet.add(Properties.DURATION); - mDuration = duration; - return this; - } - - @Override - public void setInterpolator(TimeInterpolator value) { - mPropertiesToSet.add(Properties.INTERPOLATOR); - mInterpolator = value; - } - - @Override - public void setStartDelay(long startDelay) { - mPropertiesToSet.add(Properties.START_DELAY); - mStartDelay = startDelay; - } - - @Override - public void setTarget(Object target) { - throw new RuntimeException("Not implemented"); - } - - @Override - public void setupEndValues() { - - } - - @Override - public void setupStartValues() { - } - - @Override - public void start() { - mViewPropertyAnimator = mTarget.animate(); - if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) { - mViewPropertyAnimator.translationX(mTranslationX); - } - if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) { - mViewPropertyAnimator.translationY(mTranslationY); - } - if (mPropertiesToSet.contains(Properties.SCALE_X)) { - mViewPropertyAnimator.scaleX(mScaleX); - } - if (mPropertiesToSet.contains(Properties.ROTATION_Y)) { - mViewPropertyAnimator.rotationY(mRotationY); - } - if (mPropertiesToSet.contains(Properties.SCALE_Y)) { - mViewPropertyAnimator.scaleY(mScaleY); - } - if (mPropertiesToSet.contains(Properties.ALPHA)) { - mViewPropertyAnimator.alpha(mAlpha); - } - if (mPropertiesToSet.contains(Properties.START_DELAY)) { - mViewPropertyAnimator.setStartDelay(mStartDelay); - } - if (mPropertiesToSet.contains(Properties.DURATION)) { - mViewPropertyAnimator.setDuration(mDuration); - } - if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) { - mViewPropertyAnimator.setInterpolator(mInterpolator); - } - mViewPropertyAnimator.setListener(this); - mViewPropertyAnimator.start(); - } - - public LauncherViewPropertyAnimator translationX(float value) { - mPropertiesToSet.add(Properties.TRANSLATION_X); - mTranslationX = value; - return this; - } - - public LauncherViewPropertyAnimator translationY(float value) { - mPropertiesToSet.add(Properties.TRANSLATION_Y); - mTranslationY = value; - return this; - } - - public LauncherViewPropertyAnimator scaleX(float value) { - mPropertiesToSet.add(Properties.SCALE_X); - mScaleX = value; - return this; - } - - public LauncherViewPropertyAnimator scaleY(float value) { - mPropertiesToSet.add(Properties.SCALE_Y); - mScaleY = value; - return this; - } - - public LauncherViewPropertyAnimator rotationY(float value) { - mPropertiesToSet.add(Properties.ROTATION_Y); - mRotationY = value; - return this; - } - - public LauncherViewPropertyAnimator alpha(float value) { - mPropertiesToSet.add(Properties.ALPHA); - mAlpha = value; - return this; - } -} diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java deleted file mode 100644 index ad0baf44d..000000000 --- a/src/com/android/launcher2/PagedView.java +++ /dev/null @@ -1,1913 +0,0 @@ -/* - * 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.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.InputDevice; -import android.view.KeyEvent; -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.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.Interpolator; -import android.widget.Scroller; - -import com.android.launcher.R; - -import java.util.ArrayList; - -/** - * An abstraction of the original Workspace which supports browsing through a - * sequential list of "pages" - */ -public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { - private static final String TAG = "PagedView"; - private static final boolean DEBUG = false; - 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; - - protected static final int PAGE_SNAP_ANIMATION_DURATION = 550; - protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; - protected static final float NANOTIME_DIV = 1000000000.0f; - - private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; - private static final float OVERSCROLL_DAMP_FACTOR = 0.14f; - - private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; - // The page is moved more than halfway, automatically move to the next page on touch up. - private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; - - // The following constants need to be scaled based on density. The scaled versions will be - // assigned to the corresponding member variables below. - private static final int FLING_THRESHOLD_VELOCITY = 500; - private static final int MIN_SNAP_VELOCITY = 1500; - private static final int MIN_FLING_VELOCITY = 250; - - static final int AUTOMATIC_PAGE_SPACING = -1; - - protected int mFlingThresholdVelocity; - protected int mMinFlingVelocity; - protected int mMinSnapVelocity; - - protected float mDensity; - protected float mSmoothingTime; - protected float mTouchX; - - protected boolean mFirstLayout = true; - - protected int mCurrentPage; - protected int mNextPage = INVALID_PAGE; - protected int mMaxScrollX; - protected Scroller mScroller; - private VelocityTracker mVelocityTracker; - - private float mDownMotionX; - protected float mLastMotionX; - protected float mLastMotionXRemainder; - protected float mLastMotionY; - protected float mTotalMotionX; - private int mLastScreenCenter = -1; - private int[] mChildOffsets; - private int[] mChildRelativeOffsets; - private int[] mChildOffsetsWithLayoutScale; - - 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 boolean mForceScreenScrolled = false; - - protected OnLongClickListener mLongClickListener; - - protected boolean mAllowLongPress = true; - - protected int mTouchSlop; - private int mPagingTouchSlop; - private int mMaximumVelocity; - private int mMinimumWidth; - protected int mPageSpacing; - protected int mPageLayoutPaddingTop; - protected int mPageLayoutPaddingBottom; - protected int mPageLayoutPaddingLeft; - protected int mPageLayoutPaddingRight; - protected int mPageLayoutWidthGap; - protected int mPageLayoutHeightGap; - protected int mCellCountX = 0; - protected int mCellCountY = 0; - protected boolean mCenterPagesVertically; - protected boolean mAllowOverScroll = true; - protected int mUnboundedScrollX; - protected int[] mTempVisiblePagesRange = new int[2]; - protected boolean mForceDrawAllChildrenNextFrame; - - // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise - // it is equal to the scaled overscroll position. We use a separate value so as to prevent - // the screens from continuing to translate beyond the normal bounds. - protected int mOverScrollX; - - // parameter that adjusts the layout to be optimized for pages with that scale factor - protected float mLayoutScale = 1.0f; - - protected static final int INVALID_POINTER = -1; - - protected int mActivePointerId = INVALID_POINTER; - - private PageSwitchListener mPageSwitchListener; - - protected ArrayList mDirtyPageContent; - - // 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 scrollX itself in its computeScroll method - // (SmoothPagedView does this) - protected boolean mDeferScrollUpdate = false; - - protected boolean mIsPageMoving = false; - - // All syncs and layout passes are deferred until data is ready. - protected boolean mIsDataReady = false; - - // Scrolling indicator - private ValueAnimator mScrollIndicatorAnimator; - private View mScrollIndicator; - private int mScrollIndicatorPaddingLeft; - private int mScrollIndicatorPaddingRight; - private boolean mHasScrollIndicator = true; - private boolean mShouldShowScrollIndicator = false; - private boolean mShouldShowScrollIndicatorImmediately = false; - protected static final int sScrollIndicatorFadeInDuration = 150; - protected static final int sScrollIndicatorFadeOutDuration = 650; - protected static final int sScrollIndicatorFlashDuration = 650; - - // If set, will defer loading associated pages until the scrolling settles - private boolean mDeferLoadAssociatedPagesUntilScrollCompletes; - - 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); - - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.PagedView, defStyle, 0); - setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0)); - mPageLayoutPaddingTop = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutPaddingTop, 0); - mPageLayoutPaddingBottom = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutPaddingBottom, 0); - mPageLayoutPaddingLeft = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutPaddingLeft, 0); - mPageLayoutPaddingRight = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutPaddingRight, 0); - mPageLayoutWidthGap = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutWidthGap, 0); - mPageLayoutHeightGap = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutHeightGap, 0); - mScrollIndicatorPaddingLeft = - a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0); - mScrollIndicatorPaddingRight = - a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0); - a.recycle(); - - setHapticFeedbackEnabled(false); - init(); - } - - /** - * Initializes various states for this workspace. - */ - protected void init() { - mDirtyPageContent = new ArrayList(); - mDirtyPageContent.ensureCapacity(32); - mScroller = new Scroller(getContext(), new ScrollInterpolator()); - mCurrentPage = 0; - mCenterPagesVertically = true; - - final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - mTouchSlop = configuration.getScaledTouchSlop(); - mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - mDensity = getResources().getDisplayMetrics().density; - - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); - mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); - mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); - setOnHierarchyChangeListener(this); - } - - public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { - mPageSwitchListener = pageSwitchListener; - if (mPageSwitchListener != null) { - mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); - } - } - - /** - * Called by subclasses to mark that data is ready, and that we can begin loading and laying - * out pages. - */ - protected void setDataIsReady() { - mIsDataReady = true; - } - protected boolean isDataReady() { - return mIsDataReady; - } - - /** - * Returns the index of the currently displayed page. - * - * @return The index of the currently displayed page. - */ - int getCurrentPage() { - return mCurrentPage; - } - int getNextPage() { - return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; - } - - int getPageCount() { - return getChildCount(); - } - - View getPageAt(int index) { - return getChildAt(index); - } - - protected int indexToPage(int index) { - return index; - } - - /** - * Updates the scroll of the current page immediately to its final scroll position. We use this - * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of - * the previous tab page. - */ - protected void updateCurrentPageScroll() { - int offset = getChildOffset(mCurrentPage); - int relOffset = getRelativeChildOffset(mCurrentPage); - int newX = offset - relOffset; - scrollTo(newX, 0); - mScroller.setFinalX(newX); - mScroller.forceFinished(true); - } - - /** - * Sets the current page. - */ - void setCurrentPage(int currentPage) { - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } - // don't introduce any checks like mCurrentPage == currentPage here-- if we change the - // the default - if (getChildCount() == 0) { - return; - } - - - mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); - updateCurrentPageScroll(); - updateScrollingIndicator(); - notifyPageSwitchListener(); - invalidate(); - } - - protected void notifyPageSwitchListener() { - if (mPageSwitchListener != null) { - mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); - } - } - - protected void pageBeginMoving() { - if (!mIsPageMoving) { - mIsPageMoving = true; - onPageBeginMoving(); - } - } - - protected void pageEndMoving() { - if (mIsPageMoving) { - mIsPageMoving = false; - onPageEndMoving(); - } - } - - protected boolean isPageMoving() { - return mIsPageMoving; - } - - // 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 scrollBy(int x, int y) { - scrollTo(mUnboundedScrollX + x, getScrollY() + y); - } - - @Override - public void scrollTo(int x, int y) { - mUnboundedScrollX = x; - - if (x < 0) { - super.scrollTo(0, y); - if (mAllowOverScroll) { - overScroll(x); - } - } else if (x > mMaxScrollX) { - super.scrollTo(mMaxScrollX, y); - if (mAllowOverScroll) { - overScroll(x - mMaxScrollX); - } - } else { - mOverScrollX = x; - 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()) { - // Don't bother scrolling if the page does not need to be moved - if (getScrollX() != mScroller.getCurrX() - || getScrollY() != mScroller.getCurrY() - || mOverScrollX != mScroller.getCurrX()) { - scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); - } - invalidate(); - return true; - } else if (mNextPage != INVALID_PAGE) { - mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); - mNextPage = INVALID_PAGE; - notifyPageSwitchListener(); - - // Load the associated pages if necessary - if (mDeferLoadAssociatedPagesUntilScrollCompletes) { - loadAssociatedPages(mCurrentPage); - mDeferLoadAssociatedPagesUntilScrollCompletes = false; - } - - // We don't want to trigger a page end moving unless the page has settled - // and the user has stopped scrolling - if (mTouchState == TOUCH_STATE_REST) { - pageEndMoving(); - } - - // Notify the user when the page changes - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent ev = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); - ev.getText().add(getCurrentPageDescription()); - sendAccessibilityEventUnchecked(ev); - } - return true; - } - return false; - } - - @Override - public void computeScroll() { - computeScrollHelper(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (!mIsDataReady) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - if (widthMode != MeasureSpec.EXACTLY) { - throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); - } - - // Return early if we aren't given a proper dimension - if (widthSize <= 0 || heightSize <= 0) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - - /* Allow the height to be set as WRAP_CONTENT. This allows the particular case - * of the All apps view on XLarge displays to not take up more space then it needs. Width - * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect - * each page to have the same width. - */ - int maxChildHeight = 0; - - final int verticalPadding = getPaddingTop() + getPaddingBottom(); - final int horizontalPadding = getPaddingLeft() + getPaddingRight(); - - - // The children are given the same width and height as the workspace - // unless they were set to WRAP_CONTENT - if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - // disallowing padding in paged view (just pass 0) - final View child = getPageAt(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 - horizontalPadding, childWidthMode); - final int childHeightMeasureSpec = - MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); - if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", " - + child.getMeasuredHeight()); - } - - if (heightMode == MeasureSpec.AT_MOST) { - heightSize = maxChildHeight + verticalPadding; - } - - setMeasuredDimension(widthSize, heightSize); - - // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions. - // We also wait until we set the measured dimensions before flushing the cache as well, to - // ensure that the cache is filled with good values. - invalidateCachedOffsets(); - - if (childCount > 0) { - if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", " - + getChildWidth(0)); - - // Calculate the variable page spacing if necessary - if (mPageSpacing == AUTOMATIC_PAGE_SPACING) { - // The gap between pages in the PagedView should be equal to the gap from the page - // to the edge of the screen (so it is not visible in the current screen). To - // account for unequal padding on each side of the paged view, we take the maximum - // of the left/right gap and use that as the gap between each page. - int offset = getRelativeChildOffset(0); - int spacing = Math.max(offset, widthSize - offset - - getChildAt(0).getMeasuredWidth()); - setPageSpacing(spacing); - } - } - - updateScrollingIndicatorPosition(); - - if (childCount > 0) { - mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); - } else { - mMaxScrollX = 0; - } - } - - protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) { - int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); - int delta = newX - getScrollX(); - - final int pageCount = getChildCount(); - for (int i = 0; i < pageCount; i++) { - View page = (View) getPageAt(i); - page.setX(page.getX() + delta); - } - setCurrentPage(newCurrentPage); - } - - // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a - // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and - // tightens the layout accordingly - public void setLayoutScale(float childrenScale) { - mLayoutScale = childrenScale; - invalidateCachedOffsets(); - - // Now we need to do a re-layout, but preserving absolute X and Y coordinates - int childCount = getChildCount(); - float childrenX[] = new float[childCount]; - float childrenY[] = new float[childCount]; - for (int i = 0; i < childCount; i++) { - final View child = getPageAt(i); - childrenX[i] = child.getX(); - childrenY[i] = child.getY(); - } - // Trigger a full re-layout (never just call onLayout directly!) - int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); - int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); - requestLayout(); - measure(widthSpec, heightSpec); - layout(getLeft(), getTop(), getRight(), getBottom()); - for (int i = 0; i < childCount; i++) { - final View child = getPageAt(i); - child.setX(childrenX[i]); - child.setY(childrenY[i]); - } - - // Also, the page offset has changed (since the pages are now smaller); - // update the page offset, but again preserving absolute X and Y coordinates - scrollToNewPageWithoutMovingPages(mCurrentPage); - } - - public void setPageSpacing(int pageSpacing) { - mPageSpacing = pageSpacing; - invalidateCachedOffsets(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (!mIsDataReady) { - return; - } - - if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); - final int verticalPadding = getPaddingTop() + getPaddingBottom(); - final int childCount = getChildCount(); - int childLeft = getRelativeChildOffset(0); - - for (int i = 0; i < childCount; i++) { - final View child = getPageAt(i); - if (child.getVisibility() != View.GONE) { - final int childWidth = getScaledMeasuredWidth(child); - final int childHeight = child.getMeasuredHeight(); - int childTop = getPaddingTop(); - if (mCenterPagesVertically) { - childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; - } - - if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); - child.layout(childLeft, childTop, - childLeft + child.getMeasuredWidth(), childTop + childHeight); - childLeft += childWidth + mPageSpacing; - } - } - - if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { - setHorizontalScrollBarEnabled(false); - updateCurrentPageScroll(); - setHorizontalScrollBarEnabled(true); - mFirstLayout = false; - } - } - - protected void screenScrolled(int screenCenter) { - if (isScrollingIndicatorEnabled()) { - updateScrollingIndicator(); - } - boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; - - if (mFadeInAdjacentScreens && !isInOverscroll) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (child != null) { - float scrollProgress = getScrollProgress(screenCenter, child, i); - float alpha = 1 - Math.abs(scrollProgress); - child.setAlpha(alpha); - } - } - invalidate(); - } - } - - @Override - public void onChildViewAdded(View parent, View child) { - // This ensures that when children are added, they get the correct transforms / alphas - // in accordance with any scroll effects. - mForceScreenScrolled = true; - invalidate(); - invalidateCachedOffsets(); - } - - @Override - public void onChildViewRemoved(View parent, View child) { - } - - protected void invalidateCachedOffsets() { - int count = getChildCount(); - if (count == 0) { - mChildOffsets = null; - mChildRelativeOffsets = null; - mChildOffsetsWithLayoutScale = null; - return; - } - - mChildOffsets = new int[count]; - mChildRelativeOffsets = new int[count]; - mChildOffsetsWithLayoutScale = new int[count]; - for (int i = 0; i < count; i++) { - mChildOffsets[i] = -1; - mChildRelativeOffsets[i] = -1; - mChildOffsetsWithLayoutScale[i] = -1; - } - } - - protected int getChildOffset(int index) { - int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ? - mChildOffsets : mChildOffsetsWithLayoutScale; - - if (childOffsets != null && childOffsets[index] != -1) { - return childOffsets[index]; - } else { - if (getChildCount() == 0) - return 0; - - int offset = getRelativeChildOffset(0); - for (int i = 0; i < index; ++i) { - offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing; - } - if (childOffsets != null) { - childOffsets[index] = offset; - } - return offset; - } - } - - protected int getRelativeChildOffset(int index) { - if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) { - return mChildRelativeOffsets[index]; - } else { - final int padding = getPaddingLeft() + getPaddingRight(); - final int offset = getPaddingLeft() + - (getMeasuredWidth() - padding - getChildWidth(index)) / 2; - if (mChildRelativeOffsets != null) { - mChildRelativeOffsets[index] = offset; - } - return offset; - } - } - - protected int getScaledMeasuredWidth(View child) { - // This functions are called enough times that it actually makes a difference in the - // profiler -- so just inline the max() here - final int measuredWidth = child.getMeasuredWidth(); - final int minWidth = mMinimumWidth; - final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth; - return (int) (maxWidth * mLayoutScale + 0.5f); - } - - protected void getVisiblePages(int[] range) { - final int pageCount = getChildCount(); - - if (pageCount > 0) { - final int screenWidth = getMeasuredWidth(); - int leftScreen = 0; - int rightScreen = 0; - View currPage = getPageAt(leftScreen); - while (leftScreen < pageCount - 1 && - currPage.getX() + currPage.getWidth() - - currPage.getPaddingRight() < getScrollX()) { - leftScreen++; - currPage = getPageAt(leftScreen); - } - rightScreen = leftScreen; - currPage = getPageAt(rightScreen + 1); - while (rightScreen < pageCount - 1 && - currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) { - rightScreen++; - currPage = getPageAt(rightScreen + 1); - } - range[0] = leftScreen; - range[1] = rightScreen; - } else { - range[0] = -1; - range[1] = -1; - } - } - - protected boolean shouldDrawChild(View child) { - return child.getAlpha() > 0; - } - - @Override - protected void dispatchDraw(Canvas canvas) { - int halfScreenSize = getMeasuredWidth() / 2; - // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. - // Otherwise it is equal to the scaled overscroll position. - int screenCenter = mOverScrollX + halfScreenSize; - - if (screenCenter != mLastScreenCenter || mForceScreenScrolled) { - // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can - // set it for the next frame - mForceScreenScrolled = false; - screenScrolled(screenCenter); - mLastScreenCenter = screenCenter; - } - - // Find out which screens are visible; as an optimization we only call draw on them - final int pageCount = getChildCount(); - if (pageCount > 0) { - getVisiblePages(mTempVisiblePagesRange); - final int leftScreen = mTempVisiblePagesRange[0]; - final int rightScreen = mTempVisiblePagesRange[1]; - if (leftScreen != -1 && rightScreen != -1) { - final long drawingTime = getDrawingTime(); - // Clip to the bounds - canvas.save(); - canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(), - getScrollY() + getBottom() - getTop()); - - // On certain graphics drivers, if you draw to a off-screen buffer that's not - // used, it can lead to poor performance. We were running into this when - // setChildrenLayersEnabled was called on a CellLayout; that triggered a re-draw - // of that CellLayout's hardware layer, even if that CellLayout wasn't visible. - // As a fix, below we set pages that aren't going to be rendered are to be - // View.INVISIBLE, preventing re-drawing of their hardware layer - for (int i = getChildCount() - 1; i >= 0; i--) { - final View v = getPageAt(i); - if (mForceDrawAllChildrenNextFrame || - (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) { - v.setVisibility(VISIBLE); - drawChild(canvas, v, drawingTime); - } else { - v.setVisibility(INVISIBLE); - } - } - mForceDrawAllChildrenNextFrame = false; - canvas.restore(); - } - } - } - - @Override - public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { - int page = indexToPage(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) { - return 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 views, int direction, int focusableMode) { - if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { - getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode); - } - if (direction == View.FOCUS_LEFT) { - if (mCurrentPage > 0) { - getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode); - } - } else if (direction == View.FOCUS_RIGHT){ - if (mCurrentPage < getPageCount() - 1) { - getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode); - } - } - } - - /** - * 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 = getPageAt(mCurrentPage); - currentPage.cancelLongPress(); - } - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - - /** - * Return true if a tap at (x, y) should trigger a flip to the previous page. - */ - protected boolean hitsPreviousPage(float x, float y) { - return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); - } - - /** - * Return true if a tap at (x, y) should trigger a flip to the next page. - */ - protected boolean hitsNextPage(float x, float y) { - return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); - } - - @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. - */ - acquireVelocityTrackerAndAddMovement(ev); - - // Skip touch handling if there are no pages to swipe - if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); - - /* - * 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; - mLastMotionXRemainder = 0; - mTotalMotionX = 0; - 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 && mTouchState != TOUCH_STATE_NEXT_PAGE) { - if (getChildCount() > 0) { - if (hitsPreviousPage(x, y)) { - mTouchState = TOUCH_STATE_PREV_PAGE; - } else if (hitsNextPage(x, y)) { - mTouchState = TOUCH_STATE_NEXT_PAGE; - } - } - } - break; - } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mTouchState = TOUCH_STATE_REST; - mAllowLongPress = false; - mActivePointerId = INVALID_POINTER; - releaseVelocityTracker(); - break; - - case MotionEvent.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - releaseVelocityTracker(); - 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 determineScrollingStart(MotionEvent ev) { - determineScrollingStart(ev, 1.0f); - } - - /* - * 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, float touchSlopScale) { - /* - * Locally do absolute value. mLastMotionX is set to the y value - * of the down event. - */ - final int pointerIndex = ev.findPointerIndex(mActivePointerId); - if (pointerIndex == -1) { - return; - } - 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 = Math.round(touchSlopScale * mTouchSlop); - boolean xPaged = xDiff > mPagingTouchSlop; - boolean xMoved = xDiff > touchSlop; - boolean yMoved = yDiff > touchSlop; - - if (xMoved || xPaged || yMoved) { - if (mUsePagingTouchSlop ? xPaged : xMoved) { - // Scroll if the user moved far enough along the X axis - mTouchState = TOUCH_STATE_SCROLLING; - mTotalMotionX += Math.abs(mLastMotionX - x); - mLastMotionX = x; - mLastMotionXRemainder = 0; - mTouchX = getScrollX(); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - pageBeginMoving(); - } - // Either way, cancel any pending longpress - cancelCurrentPageLongPress(); - } - } - - protected void cancelCurrentPageLongPress() { - 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 float getScrollProgress(int screenCenter, View v, int page) { - final int halfScreenSize = getMeasuredWidth() / 2; - - int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; - int delta = screenCenter - (getChildOffset(page) - - getRelativeChildOffset(page) + halfScreenSize); - - float scrollProgress = delta / (totalDistance * 1.0f); - scrollProgress = Math.min(scrollProgress, 1.0f); - scrollProgress = Math.max(scrollProgress, -1.0f); - return scrollProgress; - } - - // This curve determines how the effect of scrolling over the limits of the page dimishes - // as the user pulls further and further from the bounds - private float overScrollInfluenceCurve(float f) { - f -= 1.0f; - return f * f * f + 1.0f; - } - - protected void acceleratedOverScroll(float amount) { - int screenSize = getMeasuredWidth(); - - // We want to reach the max over scroll effect when the user has - // over scrolled half the size of the screen - float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); - - if (f == 0) return; - - // Clamp this factor, f, to -1 < f < 1 - if (Math.abs(f) >= 1) { - f /= Math.abs(f); - } - - int overScrollAmount = (int) Math.round(f * screenSize); - if (amount < 0) { - mOverScrollX = overScrollAmount; - super.scrollTo(0, getScrollY()); - } else { - mOverScrollX = mMaxScrollX + overScrollAmount; - super.scrollTo(mMaxScrollX, getScrollY()); - } - invalidate(); - } - - protected void dampedOverScroll(float amount) { - int screenSize = getMeasuredWidth(); - - float f = (amount / screenSize); - - if (f == 0) return; - f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); - - // Clamp this factor, f, to -1 < f < 1 - if (Math.abs(f) >= 1) { - f /= Math.abs(f); - } - - int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); - if (amount < 0) { - mOverScrollX = overScrollAmount; - super.scrollTo(0, getScrollY()); - } else { - mOverScrollX = mMaxScrollX + overScrollAmount; - super.scrollTo(mMaxScrollX, getScrollY()); - } - invalidate(); - } - - protected void overScroll(float amount) { - dampedOverScroll(amount); - } - - protected float maxOverScroll() { - // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not - // exceed). Used to find out how much extra wallpaper we need for the over scroll effect - float f = 1.0f; - f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); - return OVERSCROLL_DAMP_FACTOR * f; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - // Skip touch handling if there are no pages to swipe - if (getChildCount() <= 0) return super.onTouchEvent(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(); - mLastMotionXRemainder = 0; - mTotalMotionX = 0; - 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 float deltaX = mLastMotionX + mLastMotionXRemainder - x; - - mTotalMotionX += Math.abs(deltaX); - - // Only scroll and update mLastMotionX if we have moved some discrete amount. We - // keep the remainder because we are actually testing if we've moved from the last - // scrolled position (which is discrete). - if (Math.abs(deltaX) >= 1.0f) { - mTouchX += deltaX; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - if (!mDeferScrollUpdate) { - scrollBy((int) deltaX, 0); - if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); - } else { - invalidate(); - } - mLastMotionX = x; - mLastMotionXRemainder = deltaX - (int) deltaX; - } 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); - final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); - boolean isSignificantMove = Math.abs(deltaX) > pageWidth * - SIGNIFICANT_MOVE_THRESHOLD; - - mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); - - boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && - Math.abs(velocityX) > mFlingThresholdVelocity; - - // In the case that the page is moved far to one direction and then is flung - // in the opposite direction, we use a threshold to determine whether we should - // just return to the starting page, or if we should skip one further. - boolean returnToOriginalPage = false; - if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && - Math.signum(velocityX) != Math.signum(deltaX) && isFling) { - returnToOriginalPage = true; - } - - int finalPage; - // We give flings precedence over large moves, which is why we short-circuit our - // test for a large move if a fling has been registered. That is, a large - // move to the left and fling to the right will register as a fling to the right. - if (((isSignificantMove && deltaX > 0 && !isFling) || - (isFling && velocityX > 0)) && mCurrentPage > 0) { - finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; - snapToPageWithVelocity(finalPage, velocityX); - } else if (((isSignificantMove && deltaX < 0 && !isFling) || - (isFling && velocityX < 0)) && - mCurrentPage < getChildCount() - 1) { - finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; - snapToPageWithVelocity(finalPage, velocityX); - } else { - snapToDestination(); - } - } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { - // 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) { - // 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(); - } - } else { - onUnhandledTap(ev); - } - 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; - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - switch (event.getAction()) { - case MotionEvent.ACTION_SCROLL: { - // Handle mouse (or ext. device) by shifting the page depending on the scroll - final float vscroll; - final float hscroll; - if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { - vscroll = 0; - hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); - } else { - vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); - hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); - } - if (hscroll != 0 || vscroll != 0) { - if (hscroll > 0 || vscroll > 0) { - scrollRight(); - } else { - scrollLeft(); - } - return true; - } - } - } - } - return super.onGenericMotionEvent(event); - } - - 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); - mLastMotionXRemainder = 0; - mActivePointerId = ev.getPointerId(newPointerIndex); - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - } - } - - protected void onUnhandledTap(MotionEvent ev) {} - - @Override - public void requestChildFocus(View child, View focused) { - super.requestChildFocus(child, focused); - int page = indexToPage(indexOfChild(child)); - if (page >= 0 && page != getCurrentPage() && !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 + getScaledMeasuredWidth(getPageAt(i))); - if (left <= relativeOffset && relativeOffset <= right) { - return i; - } - } - return -1; - } - - protected int getChildWidth(int index) { - // This functions are called enough times that it actually makes a difference in the - // profiler -- so just inline the max() here - final int measuredWidth = getPageAt(index).getMeasuredWidth(); - final int minWidth = mMinimumWidth; - return (minWidth > measuredWidth) ? minWidth : measuredWidth; - } - - int getPageNearestToCenterOfScreen() { - int minDistanceFromScreenCenter = Integer.MAX_VALUE; - int minDistanceFromScreenCenterIndex = -1; - int screenCenter = getScrollX() + (getMeasuredWidth() / 2); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; ++i) { - View layout = (View) getPageAt(i); - int childWidth = getScaledMeasuredWidth(layout); - 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); - } - - private static class ScrollInterpolator implements Interpolator { - public ScrollInterpolator() { - } - - public float getInterpolation(float t) { - t -= 1.0f; - return t*t*t*t*t + 1; - } - } - - // We want the duration of the page snap animation to be influenced by the distance that - // the screen has to travel, however, we don't want this duration to be effected in a - // purely linear fashion. Instead, we use this method to moderate the effect that the distance - // of travel has on the overall snap duration. - float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - protected void snapToPageWithVelocity(int whichPage, int velocity) { - whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); - int halfScreenSize = getMeasuredWidth() / 2; - - if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); - if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " - + getMeasuredWidth() + ", " + getChildWidth(whichPage)); - final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); - int delta = newX - mUnboundedScrollX; - int duration = 0; - - if (Math.abs(velocity) < mMinFlingVelocity) { - // If the velocity is low enough, then treat this more as an automatic page advance - // as opposed to an apparent physical response to flinging - snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); - return; - } - - // Here we compute a "distance" that will be used in the computation of the overall - // snap duration. This is a function of the actual distance that needs to be traveled; - // we keep this value close to half screen size in order to reduce the variance in snap - // duration as a function of the distance the page needs to travel. - float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize)); - float distance = halfScreenSize + halfScreenSize * - distanceInfluenceForSnapDuration(distanceRatio); - - velocity = Math.abs(velocity); - velocity = Math.max(mMinSnapVelocity, velocity); - - // we want the page's snap velocity to approximately match the velocity at which the - // user flings, so we scale the duration by a value near to the derivative of the scroll - // interpolator at zero, ie. 5. We use 4 to make it a little slower. - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - - snapToPage(whichPage, delta, duration); - } - - 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)); - - if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); - if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", " - + getChildWidth(whichPage)); - int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); - final int sX = mUnboundedScrollX; - 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 == getPageAt(mCurrentPage)) { - focusedChild.clearFocus(); - } - - pageBeginMoving(); - awakenScrollBars(duration); - if (duration == 0) { - duration = Math.abs(delta); - } - - if (!mScroller.isFinished()) mScroller.abortAnimation(); - mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); - - // Load associated pages immediately if someone else is handling the scroll, otherwise defer - // loading associated pages until the scroll settles - if (mDeferScrollUpdate) { - loadAssociatedPages(mNextPage); - } else { - mDeferLoadAssociatedPagesUntilScrollCompletes = true; - } - notifyPageSwitchListener(); - invalidate(); - } - - 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 == getPageAt(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 CREATOR = - new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - protected void loadAssociatedPages(int page) { - loadAssociatedPages(page, false); - } - protected void loadAssociatedPages(int page, boolean immediateAndOnly) { - if (mContentIsRefreshable) { - final int count = getChildCount(); - if (page < count) { - int lowerPageBound = getAssociatedLowerPageBound(page); - int upperPageBound = getAssociatedUpperPageBound(page); - if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/" - + upperPageBound); - // First, clear any pages that should no longer be loaded - for (int i = 0; i < count; ++i) { - Page layout = (Page) getPageAt(i); - if ((i < lowerPageBound) || (i > upperPageBound)) { - if (layout.getPageChildCount() > 0) { - layout.removeAllViewsOnPage(); - } - mDirtyPageContent.set(i, true); - } - } - // Next, load any new pages - for (int i = 0; i < count; ++i) { - if ((i != page) && immediateAndOnly) { - continue; - } - if (lowerPageBound <= i && i <= upperPageBound) { - if (mDirtyPageContent.get(i)) { - syncPageItems(i, (i == page) && immediateAndOnly); - mDirtyPageContent.set(i, false); - } - } - } - } - } - } - - 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); - } - - /** - * 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, boolean immediate); - - protected void invalidatePageData() { - invalidatePageData(-1, false); - } - protected void invalidatePageData(int currentPage) { - invalidatePageData(currentPage, false); - } - protected void invalidatePageData(int currentPage, boolean immediateAndOnly) { - if (!mIsDataReady) { - return; - } - - if (mContentIsRefreshable) { - // Force all scrolling-related behavior to end - mScroller.forceFinished(true); - mNextPage = INVALID_PAGE; - - // Update all the pages - syncPages(); - - // We must force a measure after we've loaded the pages to update the content width and - // to determine the full scroll width - measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - - // Set a new page as the current page if necessary - if (currentPage > -1) { - setCurrentPage(Math.min(getPageCount() - 1, currentPage)); - } - - // 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, immediateAndOnly); - requestLayout(); - } - } - - protected View getScrollingIndicator() { - // We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator - // found - if (mHasScrollIndicator && mScrollIndicator == null) { - ViewGroup parent = (ViewGroup) getParent(); - if (parent != null) { - mScrollIndicator = (View) (parent.findViewById(R.id.paged_view_indicator)); - mHasScrollIndicator = mScrollIndicator != null; - if (mHasScrollIndicator) { - mScrollIndicator.setVisibility(View.VISIBLE); - } - } - } - return mScrollIndicator; - } - - protected boolean isScrollingIndicatorEnabled() { - return !LauncherApplication.isScreenLarge(); - } - - Runnable hideScrollingIndicatorRunnable = new Runnable() { - @Override - public void run() { - hideScrollingIndicator(false); - } - }; - protected void flashScrollingIndicator(boolean animated) { - removeCallbacks(hideScrollingIndicatorRunnable); - showScrollingIndicator(!animated); - postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration); - } - - protected void showScrollingIndicator(boolean immediately) { - mShouldShowScrollIndicator = true; - mShouldShowScrollIndicatorImmediately = true; - if (getChildCount() <= 1) return; - if (!isScrollingIndicatorEnabled()) return; - - mShouldShowScrollIndicator = false; - getScrollingIndicator(); - if (mScrollIndicator != null) { - // Fade the indicator in - updateScrollingIndicatorPosition(); - mScrollIndicator.setVisibility(View.VISIBLE); - cancelScrollingIndicatorAnimations(); - if (immediately) { - mScrollIndicator.setAlpha(1f); - } else { - mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f); - mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration); - mScrollIndicatorAnimator.start(); - } - } - } - - protected void cancelScrollingIndicatorAnimations() { - if (mScrollIndicatorAnimator != null) { - mScrollIndicatorAnimator.cancel(); - } - } - - protected void hideScrollingIndicator(boolean immediately) { - if (getChildCount() <= 1) return; - if (!isScrollingIndicatorEnabled()) return; - - getScrollingIndicator(); - if (mScrollIndicator != null) { - // Fade the indicator out - updateScrollingIndicatorPosition(); - cancelScrollingIndicatorAnimations(); - if (immediately) { - mScrollIndicator.setVisibility(View.INVISIBLE); - mScrollIndicator.setAlpha(0f); - } else { - mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f); - mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration); - mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { - private boolean cancelled = false; - @Override - public void onAnimationCancel(android.animation.Animator animation) { - cancelled = true; - } - @Override - public void onAnimationEnd(Animator animation) { - if (!cancelled) { - mScrollIndicator.setVisibility(View.INVISIBLE); - } - } - }); - mScrollIndicatorAnimator.start(); - } - } - } - - /** - * To be overridden by subclasses to determine whether the scroll indicator should stretch to - * fill its space on the track or not. - */ - protected boolean hasElasticScrollIndicator() { - return true; - } - - private void updateScrollingIndicator() { - if (getChildCount() <= 1) return; - if (!isScrollingIndicatorEnabled()) return; - - getScrollingIndicator(); - if (mScrollIndicator != null) { - updateScrollingIndicatorPosition(); - } - if (mShouldShowScrollIndicator) { - showScrollingIndicator(mShouldShowScrollIndicatorImmediately); - } - } - - private void updateScrollingIndicatorPosition() { - if (!isScrollingIndicatorEnabled()) return; - if (mScrollIndicator == null) return; - int numPages = getChildCount(); - int pageWidth = getMeasuredWidth(); - int lastChildIndex = Math.max(0, getChildCount() - 1); - int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); - int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; - int indicatorWidth = mScrollIndicator.getMeasuredWidth() - - mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight(); - - float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX)); - int indicatorSpace = trackWidth / numPages; - int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft; - if (hasElasticScrollIndicator()) { - if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) { - mScrollIndicator.getLayoutParams().width = indicatorSpace; - mScrollIndicator.requestLayout(); - } - } else { - int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2; - indicatorPos += indicatorCenterOffset; - } - mScrollIndicator.setTranslationX(indicatorPos); - } - - public void showScrollIndicatorTrack() { - } - - public void hideScrollIndicatorTrack() { - } - - /* Accessibility */ - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setScrollable(getPageCount() > 1); - if (getCurrentPage() < getPageCount() - 1) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - } - if (getCurrentPage() > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - event.setFromIndex(mCurrentPage); - event.setToIndex(mCurrentPage); - event.setItemCount(getChildCount()); - } - } - - @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { - return true; - } - switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (getCurrentPage() < getPageCount() - 1) { - scrollRight(); - return true; - } - } break; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (getCurrentPage() > 0) { - scrollLeft(); - return true; - } - } break; - } - return false; - } - - protected String getCurrentPageDescription() { - return String.format(getContext().getString(R.string.default_scroll_format), - getNextPage() + 1, getChildCount()); - } - - @Override - public boolean onHoverEvent(android.view.MotionEvent event) { - return true; - } -} diff --git a/src/com/android/launcher2/PagedViewCellLayout.java b/src/com/android/launcher2/PagedViewCellLayout.java deleted file mode 100644 index 6f73e6341..000000000 --- a/src/com/android/launcher2/PagedViewCellLayout.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * 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.Resources; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewDebug; -import android.view.ViewGroup; - -import com.android.launcher.R; - -/** - * 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 implements Page { - static final String TAG = "PagedViewCellLayout"; - - private int mCellCountX; - private int mCellCountY; - private int mOriginalCellWidth; - private int mOriginalCellHeight; - private int mCellWidth; - private int mCellHeight; - private int mOriginalWidthGap; - private int mOriginalHeightGap; - private int mWidthGap; - private int mHeightGap; - private int mMaxGap; - protected PagedViewCellLayoutChildren mChildren; - - 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 - Resources resources = context.getResources(); - mOriginalCellWidth = mCellWidth = - resources.getDimensionPixelSize(R.dimen.apps_customize_cell_width); - mOriginalCellHeight = mCellHeight = - resources.getDimensionPixelSize(R.dimen.apps_customize_cell_height); - mCellCountX = LauncherModel.getCellCountX(); - mCellCountY = LauncherModel.getCellCountY(); - mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1; - mMaxGap = resources.getDimensionPixelSize(R.dimen.apps_customize_max_gap); - - mChildren = new PagedViewCellLayoutChildren(context); - mChildren.setCellDimensions(mCellWidth, mCellHeight); - mChildren.setGap(mWidthGap, mHeightGap); - - addView(mChildren); - } - - public int getCellWidth() { - return mCellWidth; - } - - public int getCellHeight() { - return mCellHeight; - } - - void destroyHardwareLayers() { - // called when a page is no longer visible (triggered by loadAssociatedPages -> - // removeAllViewsOnPage) - setLayerType(LAYER_TYPE_NONE, null); - } - - void createHardwareLayers() { - // called when a page is visible (triggered by loadAssociatedPages -> syncPageItems) - setLayerType(LAYER_TYPE_HARDWARE, null); - } - - @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); - mChildren.addView(child, index, lp); - - return true; - } - return false; - } - - @Override - public void removeAllViewsOnPage() { - mChildren.removeAllViews(); - destroyHardwareLayers(); - } - - @Override - public void removeViewOnPageAt(int index) { - mChildren.removeViewAt(index); - } - - /** - * Clears all the key listeners for the individual icons. - */ - public void resetChildrenOnKeyListeners() { - int childCount = mChildren.getChildCount(); - for (int j = 0; j < childCount; ++j) { - mChildren.getChildAt(j).setOnKeyListener(null); - } - } - - @Override - public int getPageChildCount() { - return mChildren.getChildCount(); - } - - public PagedViewCellLayoutChildren getChildrenLayout() { - return mChildren; - } - - @Override - public View getChildOnPageAt(int i) { - return mChildren.getChildAt(i); - } - - @Override - public int indexOfChildOnPage(View v) { - return mChildren.indexOfChild(v); - } - - public int getCellCountX() { - return mCellCountX; - } - - public int getCellCountY() { - return mCellCountY; - } - - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - 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"); - } - - int numWidthGaps = mCellCountX - 1; - int numHeightGaps = mCellCountY - 1; - - if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { - int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight(); - int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); - int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth); - int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight); - mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); - mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); - - mChildren.setGap(mWidthGap, mHeightGap); - } else { - mWidthGap = mOriginalWidthGap; - mHeightGap = mOriginalHeightGap; - } - - // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY - int newWidth = widthSpecSize; - int newHeight = heightSpecSize; - if (widthSpecMode == MeasureSpec.AT_MOST) { - newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) + - ((mCellCountX - 1) * mWidthGap); - newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) + - ((mCellCountY - 1) * mHeightGap); - setMeasuredDimension(newWidth, newHeight); - } - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - int childWidthMeasureSpec = - MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() - - getPaddingRight(), MeasureSpec.EXACTLY); - int childheightMeasureSpec = - MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() - - getPaddingBottom(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - } - - setMeasuredDimension(newWidth, newHeight); - } - - int getContentWidth() { - return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight(); - } - - int getContentHeight() { - if (mCellCountY > 0) { - return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); - } - return 0; - } - - int getWidthBeforeFirstLayout() { - if (mCellCountX > 0) { - return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap); - } - return 0; - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - child.layout(getPaddingLeft(), getPaddingTop(), - r - l - getPaddingRight(), b - t - getPaddingBottom()); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = super.onTouchEvent(event); - int count = getPageChildCount(); - if (count > 0) { - // We only intercept the touch if we are tapping in empty space after the final row - View child = getChildOnPageAt(count - 1); - int bottom = child.getBottom(); - int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX()); - if (numRows < getCellCountY()) { - // Add a little bit of buffer if there is room for another row - bottom += mCellHeight / 2; - } - result = result || (event.getY() < bottom); - } - return result; - } - - public void enableCenteredContent(boolean enabled) { - mChildren.enableCenteredContent(enabled); - } - - @Override - protected void setChildrenDrawingCacheEnabled(boolean enabled) { - mChildren.setChildrenDrawingCacheEnabled(enabled); - } - - public void setCellCount(int xCount, int yCount) { - mCellCountX = xCount; - mCellCountY = yCount; - requestLayout(); - } - - public void setGap(int widthGap, int heightGap) { - mOriginalWidthGap = mWidthGap = widthGap; - mOriginalHeightGap = mHeightGap = heightGap; - mChildren.setGap(widthGap, heightGap); - } - - 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) { - // We don't show the next/previous pages any more, so we use the full width, minus the - // padding - int availWidth = width - (getPaddingLeft() + getPaddingRight()); - - // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N - int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); - - // We don't do anything fancy to determine if we squeeze another row in. - return n; - } - - /** - * Estimates the number of cells that the specified height would take up. - */ - public int estimateCellVSpan(int height) { - // The space for a page is the height - top padding (current page) - bottom padding (current - // page) - int availHeight = height - (getPaddingTop() + getPaddingBottom()); - - // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N - int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); - - // We don't do anything fancy to determine if we squeeze another row in. - return n; - } - - /** Returns an estimated center position of the cell at the specified index */ - public int[] estimateCellPosition(int x, int y) { - return new int[] { - getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2), - getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2) - }; - } - - public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { - mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); - mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); - requestLayout(); - } - - /** - * Estimates the width that the number of hSpan 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; - - if (LauncherApplication.isScreenLarge()) { - x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; - y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; - } else { - x = myCellX * (cellWidth + widthGap) + leftMargin; - y = 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 + ")"; - } - } -} - -interface Page { - public int getPageChildCount(); - public View getChildOnPageAt(int i); - public void removeAllViewsOnPage(); - public void removeViewOnPageAt(int i); - public int indexOfChildOnPage(View v); -} diff --git a/src/com/android/launcher2/PagedViewCellLayoutChildren.java b/src/com/android/launcher2/PagedViewCellLayoutChildren.java deleted file mode 100644 index 187a22d55..000000000 --- a/src/com/android/launcher2/PagedViewCellLayoutChildren.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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.Rect; -import android.view.View; -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 PagedViewCellLayoutChildren extends ViewGroup { - static final String TAG = "PagedViewCellLayout"; - - private boolean mCenterContent; - - private int mCellWidth; - private int mCellHeight; - private int mWidthGap; - private int mHeightGap; - - public PagedViewCellLayoutChildren(Context context) { - super(context); - } - - @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 void setGap(int widthGap, int heightGap) { - mWidthGap = widthGap; - mHeightGap = heightGap; - requestLayout(); - } - - public void setCellDimensions(int width, int height) { - mCellWidth = width; - mCellHeight = height; - requestLayout(); - } - - @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) { - 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 count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - PagedViewCellLayout.LayoutParams lp = - (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); - lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, - getPaddingLeft(), - getPaddingTop()); - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, - MeasureSpec.EXACTLY); - int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, - MeasureSpec.EXACTLY); - - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - } - - setMeasuredDimension(widthSpecSize, heightSpecSize); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int count = getChildCount(); - - int offsetX = 0; - if (mCenterContent && count > 0) { - // determine the max width of all the rows and center accordingly - int maxRowX = 0; - int minRowX = Integer.MAX_VALUE; - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - PagedViewCellLayout.LayoutParams lp = - (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); - minRowX = Math.min(minRowX, lp.x); - maxRowX = Math.max(maxRowX, lp.x + lp.width); - } - } - int maxRowWidth = maxRowX - minRowX; - offsetX = (getMeasuredWidth() - 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); - } - } - } - - 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); - } - } - } -} diff --git a/src/com/android/launcher2/PagedViewGridLayout.java b/src/com/android/launcher2/PagedViewGridLayout.java deleted file mode 100644 index 90bfe88ec..000000000 --- a/src/com/android/launcher2/PagedViewGridLayout.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.GridLayout; - -/** - * The grid based layout used strictly for the widget/wallpaper tab of the AppsCustomize pane - */ -public class PagedViewGridLayout extends GridLayout implements Page { - static final String TAG = "PagedViewGridLayout"; - - private int mCellCountX; - private int mCellCountY; - private Runnable mOnLayoutListener; - - public PagedViewGridLayout(Context context, int cellCountX, int cellCountY) { - super(context, null, 0); - mCellCountX = cellCountX; - mCellCountY = cellCountY; - } - - int getCellCountX() { - return mCellCountX; - } - - int getCellCountY() { - return mCellCountY; - } - - /** - * Clears all the key listeners for the individual widgets. - */ - public void resetChildrenOnKeyListeners() { - int childCount = getChildCount(); - for (int j = 0; j < childCount; ++j) { - getChildAt(j).setOnKeyListener(null); - } - } - - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // PagedView currently has issues with different-sized pages since it calculates the - // offset of each page to scroll to before it updates the actual size of each page - // (which can change depending on the content if the contents aren't a fixed size). - // We work around this by having a minimum size on each widget page). - int widthSpecSize = Math.min(getSuggestedMinimumWidth(), - MeasureSpec.getSize(widthMeasureSpec)); - int widthSpecMode = MeasureSpec.EXACTLY; - super.onMeasure(MeasureSpec.makeMeasureSpec(widthSpecSize, widthSpecMode), - heightMeasureSpec); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mOnLayoutListener = null; - } - - public void setOnLayoutListener(Runnable r) { - mOnLayoutListener = r; - } - - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (mOnLayoutListener != null) { - mOnLayoutListener.run(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = super.onTouchEvent(event); - int count = getPageChildCount(); - if (count > 0) { - // We only intercept the touch if we are tapping in empty space after the final row - View child = getChildOnPageAt(count - 1); - int bottom = child.getBottom(); - result = result || (event.getY() < bottom); - } - return result; - } - - void destroyHardwareLayer() { - setLayerType(LAYER_TYPE_NONE, null); - } - - void createHardwareLayer() { - setLayerType(LAYER_TYPE_HARDWARE, null); - } - - @Override - public void removeAllViewsOnPage() { - removeAllViews(); - mOnLayoutListener = null; - destroyHardwareLayer(); - } - - @Override - public void removeViewOnPageAt(int index) { - removeViewAt(index); - } - - @Override - public int getPageChildCount() { - return getChildCount(); - } - - @Override - public View getChildOnPageAt(int i) { - return getChildAt(i); - } - - @Override - public int indexOfChildOnPage(View v) { - return indexOfChild(v); - } - - public static class LayoutParams extends FrameLayout.LayoutParams { - public LayoutParams(int width, int height) { - super(width, height); - } - } -} diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java deleted file mode 100644 index d2aa31f86..000000000 --- a/src/com/android/launcher2/PagedViewIcon.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.util.AttributeSet; -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 { - /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */ - public static interface PressedCallback { - void iconPressed(PagedViewIcon icon); - } - - @SuppressWarnings("unused") - private static final String TAG = "PagedViewIcon"; - private static final float PRESS_ALPHA = 0.4f; - - private PagedViewIcon.PressedCallback mPressedCallback; - private boolean mLockDrawableState = false; - - private Bitmap mIcon; - - 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); - } - - public void applyFromApplicationInfo(ApplicationInfo info, boolean scaleUp, - PagedViewIcon.PressedCallback cb) { - mIcon = info.iconBitmap; - mPressedCallback = cb; - setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null); - setText(info.title); - setTag(info); - } - - public void lockDrawableState() { - mLockDrawableState = true; - } - - public void resetDrawableState() { - mLockDrawableState = false; - post(new Runnable() { - @Override - public void run() { - refreshDrawableState(); - } - }); - } - - protected void drawableStateChanged() { - super.drawableStateChanged(); - - // We keep in the pressed state until resetDrawableState() is called to reset the press - // feedback - if (isPressed()) { - setAlpha(PRESS_ALPHA); - if (mPressedCallback != null) { - mPressedCallback.iconPressed(this); - } - } else if (!mLockDrawableState) { - setAlpha(1f); - } - } -} diff --git a/src/com/android/launcher2/PagedViewIconCache.java b/src/com/android/launcher2/PagedViewIconCache.java deleted file mode 100644 index d65f68baf..000000000 --- a/src/com/android/launcher2/PagedViewIconCache.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.pm.ComponentInfo; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; - -/** - * Simple cache mechanism for PagedView outlines. - */ -public class PagedViewIconCache { - public static class Key { - public enum Type { - ApplicationInfoKey, - AppWidgetProviderInfoKey, - ResolveInfoKey - } - private final ComponentName mComponentName; - private final Type mType; - - public Key(ApplicationInfo info) { - mComponentName = info.componentName; - mType = Type.ApplicationInfoKey; - } - public Key(ResolveInfo info) { - final ComponentInfo ci = info.activityInfo != null ? info.activityInfo : - info.serviceInfo; - mComponentName = new ComponentName(ci.packageName, ci.name); - mType = Type.ResolveInfoKey; - } - public Key(AppWidgetProviderInfo info) { - mComponentName = info.provider; - mType = Type.AppWidgetProviderInfoKey; - } - - private ComponentName getComponentName() { - return mComponentName; - } - public boolean isKeyType(Type t) { - return (mType == t); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Key) { - Key k = (Key) o; - return mComponentName.equals(k.mComponentName); - } - return super.equals(o); - } - @Override - public int hashCode() { - return getComponentName().hashCode(); - } - } - - private final HashMap mIconOutlineCache = new HashMap(); - - public void clear() { - for (Key key : mIconOutlineCache.keySet()) { - mIconOutlineCache.get(key).recycle(); - } - mIconOutlineCache.clear(); - } - private void retainAll(HashSet keysToKeep, Key.Type t) { - HashSet keysToRemove = new HashSet(mIconOutlineCache.keySet()); - keysToRemove.removeAll(keysToKeep); - for (Key key : keysToRemove) { - if (key.isKeyType(t)) { - mIconOutlineCache.get(key).recycle(); - mIconOutlineCache.remove(key); - } - } - } - /** Removes all the keys to applications that aren't in the passed in collection */ - public void retainAllApps(ArrayList keys) { - HashSet keysSet = new HashSet(); - for (ApplicationInfo info : keys) { - keysSet.add(new Key(info)); - } - retainAll(keysSet, Key.Type.ApplicationInfoKey); - } - /** Removes all the keys to shortcuts that aren't in the passed in collection */ - public void retainAllShortcuts(List keys) { - HashSet keysSet = new HashSet(); - for (ResolveInfo info : keys) { - keysSet.add(new Key(info)); - } - retainAll(keysSet, Key.Type.ResolveInfoKey); - } - /** Removes all the keys to widgets that aren't in the passed in collection */ - public void retainAllAppWidgets(List keys) { - HashSet keysSet = new HashSet(); - for (AppWidgetProviderInfo info : keys) { - keysSet.add(new Key(info)); - } - retainAll(keysSet, Key.Type.AppWidgetProviderInfoKey); - } - public void addOutline(Key key, Bitmap b) { - mIconOutlineCache.put(key, b); - } - public void removeOutline(Key key) { - if (mIconOutlineCache.containsKey(key)) { - mIconOutlineCache.get(key).recycle(); - mIconOutlineCache.remove(key); - } - } - public Bitmap getOutline(Key key) { - return mIconOutlineCache.get(key); - } -} diff --git a/src/com/android/launcher2/PagedViewWidget.java b/src/com/android/launcher2/PagedViewWidget.java deleted file mode 100644 index 66b7080d4..000000000 --- a/src/com/android/launcher2/PagedViewWidget.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.Context; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.launcher.R; - -/** - * The linear layout used strictly for the widget/wallpaper tab of the customization tray - */ -public class PagedViewWidget extends LinearLayout { - static final String TAG = "PagedViewWidgetLayout"; - - private static boolean sDeletePreviewsWhenDetachedFromWindow = true; - - private String mDimensionsFormatString; - CheckForShortPress mPendingCheckForShortPress = null; - ShortPressListener mShortPressListener = null; - boolean mShortPressTriggered = false; - static PagedViewWidget sShortpressTarget = null; - boolean mIsAppWidget; - - public PagedViewWidget(Context context) { - this(context, null); - } - - public PagedViewWidget(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PagedViewWidget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - final Resources r = context.getResources(); - mDimensionsFormatString = r.getString(R.string.widget_dims_format); - - setWillNotDraw(false); - setClipToPadding(false); - } - - public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) { - sDeletePreviewsWhenDetachedFromWindow = value; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (sDeletePreviewsWhenDetachedFromWindow) { - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - if (image != null) { - FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable(); - if (preview != null && preview.getBitmap() != null) { - preview.getBitmap().recycle(); - } - image.setImageDrawable(null); - } - } - } - - public void applyFromAppWidgetProviderInfo(AppWidgetProviderInfo info, - int maxWidth, int[] cellSpan) { - mIsAppWidget = true; - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - if (maxWidth > -1) { - image.setMaxWidth(maxWidth); - } - image.setContentDescription(info.label); - final TextView name = (TextView) findViewById(R.id.widget_name); - name.setText(info.label); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - int hSpan = Math.min(cellSpan[0], LauncherModel.getCellCountX()); - int vSpan = Math.min(cellSpan[1], LauncherModel.getCellCountY()); - dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); - } - } - - public void applyFromResolveInfo(PackageManager pm, ResolveInfo info) { - mIsAppWidget = false; - CharSequence label = info.loadLabel(pm); - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - image.setContentDescription(label); - final TextView name = (TextView) findViewById(R.id.widget_name); - name.setText(label); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - dims.setText(String.format(mDimensionsFormatString, 1, 1)); - } - } - - public int[] getPreviewSize() { - final ImageView i = (ImageView) findViewById(R.id.widget_preview); - int[] maxSize = new int[2]; - maxSize[0] = i.getWidth() - i.getPaddingLeft() - i.getPaddingRight(); - maxSize[1] = i.getHeight() - i.getPaddingTop(); - return maxSize; - } - - void applyPreview(FastBitmapDrawable preview, int index) { - final PagedViewWidgetImageView image = - (PagedViewWidgetImageView) findViewById(R.id.widget_preview); - if (preview != null) { - image.mAllowRequestLayout = false; - image.setImageDrawable(preview); - if (mIsAppWidget) { - // center horizontally - int[] imageSize = getPreviewSize(); - int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; - image.setPadding(image.getPaddingLeft() + centerAmount, - image.getPaddingTop(), - image.getPaddingRight(), - image.getPaddingBottom()); - } - image.setAlpha(1f); - image.mAllowRequestLayout = true; - } - } - - void setShortPressListener(ShortPressListener listener) { - mShortPressListener = listener; - } - - interface ShortPressListener { - void onShortPress(View v); - void cleanUpShortPress(View v); - } - - class CheckForShortPress implements Runnable { - public void run() { - if (sShortpressTarget != null) return; - if (mShortPressListener != null) { - mShortPressListener.onShortPress(PagedViewWidget.this); - sShortpressTarget = PagedViewWidget.this; - } - mShortPressTriggered = true; - } - } - - private void checkForShortPress() { - if (sShortpressTarget != null) return; - if (mPendingCheckForShortPress == null) { - mPendingCheckForShortPress = new CheckForShortPress(); - } - postDelayed(mPendingCheckForShortPress, 120); - } - - /** - * Remove the longpress detection timer. - */ - private void removeShortPressCallback() { - if (mPendingCheckForShortPress != null) { - removeCallbacks(mPendingCheckForShortPress); - } - } - - private void cleanUpShortPress() { - removeShortPressCallback(); - if (mShortPressTriggered) { - if (mShortPressListener != null) { - mShortPressListener.cleanUpShortPress(PagedViewWidget.this); - } - mShortPressTriggered = false; - } - } - - static void resetShortPressTarget() { - sShortpressTarget = null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - - switch (event.getAction()) { - case MotionEvent.ACTION_UP: - cleanUpShortPress(); - break; - case MotionEvent.ACTION_DOWN: - checkForShortPress(); - break; - case MotionEvent.ACTION_CANCEL: - cleanUpShortPress(); - break; - case MotionEvent.ACTION_MOVE: - break; - } - - // 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 true; - } -} diff --git a/src/com/android/launcher2/PagedViewWidgetImageView.java b/src/com/android/launcher2/PagedViewWidgetImageView.java deleted file mode 100644 index 22db0abd8..000000000 --- a/src/com/android/launcher2/PagedViewWidgetImageView.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Insets; -import android.util.AttributeSet; -import android.widget.ImageView; - - - -class PagedViewWidgetImageView extends ImageView { - public boolean mAllowRequestLayout = true; - - public PagedViewWidgetImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void requestLayout() { - if (mAllowRequestLayout) { - super.requestLayout(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - - Insets insets = Insets.NONE; - if (getBackground() != null) { - insets = getBackground().getLayoutInsets(); - } - canvas.save(); - canvas.clipRect(getScrollX() + getPaddingLeft() + insets.left, - getScrollY() + getPaddingTop() + insets.top, - getScrollX() + getRight() - getLeft() - getPaddingRight() - insets.right, - getScrollY() + getBottom() - getTop() - getPaddingBottom() - insets.bottom); - - super.onDraw(canvas); - canvas.restore(); - - } -} diff --git a/src/com/android/launcher2/PagedViewWithDraggableItems.java b/src/com/android/launcher2/PagedViewWithDraggableItems.java deleted file mode 100644 index 22fd82b69..000000000 --- a/src/com/android/launcher2/PagedViewWithDraggableItems.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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.view.View; - - -/* Class that does most of the work of enabling dragging items out of a PagedView by performing a - * vertical drag. Used by both CustomizePagedView and AllAppsPagedView. - * Subclasses must do the following: - * * call setDragSlopeThreshold after making an instance of the PagedViewWithDraggableItems - * * call child.setOnLongClickListener(this) and child.setOnTouchListener(this) on all children - * (good place to do it is in syncPageItems) - * * override beginDragging(View) (but be careful to call super.beginDragging(View) - * - */ -public abstract class PagedViewWithDraggableItems extends PagedView - implements View.OnLongClickListener, View.OnTouchListener { - private View mLastTouchedItem; - private boolean mIsDragging; - private boolean mIsDragEnabled; - private float mDragSlopeThreshold; - private Launcher mLauncher; - - public PagedViewWithDraggableItems(Context context) { - this(context, null); - } - - public PagedViewWithDraggableItems(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PagedViewWithDraggableItems(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mLauncher = (Launcher) context; - } - - protected boolean beginDragging(View v) { - boolean wasDragging = mIsDragging; - mIsDragging = true; - return !wasDragging; - } - - protected void cancelDragging() { - mIsDragging = false; - mLastTouchedItem = null; - mIsDragEnabled = false; - } - - private void handleTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - cancelDragging(); - mIsDragEnabled = true; - break; - case MotionEvent.ACTION_MOVE: - if (mTouchState != TOUCH_STATE_SCROLLING && !mIsDragging && mIsDragEnabled) { - determineDraggingStart(ev); - } - break; - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - handleTouchEvent(ev); - return super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - handleTouchEvent(ev); - return super.onTouchEvent(ev); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - mLastTouchedItem = v; - mIsDragEnabled = true; - return false; - } - - @Override - public boolean onLongClick(View v) { - // Return early if this is not initiated from a touch - if (!v.isInTouchMode()) return false; - // Return early if we are still animating the pages - if (mNextPage != INVALID_PAGE) return false; - // When we have exited all apps or are in transition, disregard long clicks - if (!mLauncher.isAllAppsCustomizeOpen() || - mLauncher.getWorkspace().isSwitchingState()) return false; - // Return if global dragging is not enabled - if (!mLauncher.isDraggingEnabled()) return false; - - return beginDragging(v); - } - - /* - * 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) { - if (!mIsDragging) super.determineScrollingStart(ev); - } - - /* - * Determines if we should change the touch state to start dragging after the - * user moves their touch point far enough. - */ - protected void determineDraggingStart(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 yMoved = yDiff > touchSlop; - boolean isUpwardMotion = (yDiff / (float) xDiff) > mDragSlopeThreshold; - - if (isUpwardMotion && yMoved && mLastTouchedItem != null) { - // Drag if the user moved far enough along the Y axis - beginDragging(mLastTouchedItem); - - // Cancel any pending long press - 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(); - } - } - } - } - - public void setDragSlopeThreshold(float dragSlopeThreshold) { - mDragSlopeThreshold = dragSlopeThreshold; - } - - @Override - protected void onDetachedFromWindow() { - cancelDragging(); - super.onDetachedFromWindow(); - } - - /** Show the scrolling indicators when we move the page */ - protected void onPageBeginMoving() { - showScrollingIndicator(false); - } - protected void onPageEndMoving() { - hideScrollingIndicator(false); - } -} diff --git a/src/com/android/launcher2/PendingAddItemInfo.java b/src/com/android/launcher2/PendingAddItemInfo.java deleted file mode 100644 index 9a133ed27..000000000 --- a/src/com/android/launcher2/PendingAddItemInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.AppWidgetHostView; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.pm.ActivityInfo; -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 PendingAddShortcutInfo extends PendingAddItemInfo { - - ActivityInfo shortcutActivityInfo; - - public PendingAddShortcutInfo(ActivityInfo activityInfo) { - shortcutActivityInfo = activityInfo; - } - - @Override - public String toString() { - return "Shortcut: " + shortcutActivityInfo.packageName; - } -} - -class PendingAddWidgetInfo extends PendingAddItemInfo { - int minWidth; - int minHeight; - int minResizeWidth; - int minResizeHeight; - int previewImage; - int icon; - AppWidgetProviderInfo info; - AppWidgetHostView boundWidget; - - // 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; - this.info = i; - componentName = i.provider; - minWidth = i.minWidth; - minHeight = i.minHeight; - minResizeWidth = i.minResizeWidth; - minResizeHeight = i.minResizeHeight; - previewImage = i.previewImage; - icon = i.icon; - if (dataMimeType != null && data != null) { - mimeType = dataMimeType; - configurationData = data; - } - } - - // Copy constructor - public PendingAddWidgetInfo(PendingAddWidgetInfo copy) { - minWidth = copy.minWidth; - minHeight = copy.minHeight; - minResizeWidth = copy.minResizeWidth; - minResizeHeight = copy.minResizeHeight; - previewImage = copy.previewImage; - icon = copy.icon; - info = copy.info; - boundWidget = copy.boundWidget; - mimeType = copy.mimeType; - configurationData = copy.configurationData; - componentName = copy.componentName; - itemType = copy.itemType; - spanX = copy.spanX; - spanY = copy.spanY; - minSpanX = copy.minSpanX; - minSpanY = copy.minSpanY; - } - - @Override - public String toString() { - return "Widget: " + componentName.toShortString(); - } -} diff --git a/src/com/android/launcher2/PreloadReceiver.java b/src/com/android/launcher2/PreloadReceiver.java deleted file mode 100644 index d1bc6393d..000000000 --- a/src/com/android/launcher2/PreloadReceiver.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2012 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.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -public class PreloadReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - final LauncherApplication app = (LauncherApplication) context.getApplicationContext(); - final LauncherProvider provider = app.getLauncherProvider(); - if (provider != null) { - new Thread(new Runnable() { - public void run() { - provider.loadDefaultFavoritesIfNecessary(); - } - }).start(); - } - } -} diff --git a/src/com/android/launcher2/RocketLauncher.java b/src/com/android/launcher2/RocketLauncher.java deleted file mode 100644 index 268769d2f..000000000 --- a/src/com/android/launcher2/RocketLauncher.java +++ /dev/null @@ -1,417 +0,0 @@ -/*); - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// TODO: -// background stellar matter: -// - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift - -package com.android.launcher2; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.TimeAnimator; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; -import android.support.v13.dreams.BasicDream; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.launcher.R; - -import java.util.HashMap; -import java.util.Random; - -public class RocketLauncher extends BasicDream { - public static final boolean ROCKET_LAUNCHER = true; - - public static class Board extends FrameLayout - { - public static final boolean FIXED_STARS = true; - public static final boolean FLYING_STARS = true; - public static final int NUM_ICONS = 20; - - public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed - private boolean mManeuveringThrusters = false; - private float mSpeedScale = 1.0f; - - public static final int LAUNCH_ZOOM_TIME = 400; // ms - - HashMap mIcons; - ComponentName[] mComponentNames; - - static Random sRNG = new Random(); - - static float lerp(float a, float b, float f) { - return (b-a)*f + a; - } - - static float randfrange(float a, float b) { - return lerp(a, b, sRNG.nextFloat()); - } - - static int randsign() { - return sRNG.nextBoolean() ? 1 : -1; - } - - static E pick(E[] array) { - if (array.length == 0) return null; - return array[sRNG.nextInt(array.length)]; - } - - public class FlyingIcon extends ImageView { - public static final float VMAX = 1000.0f; - public static final float VMIN = 100.0f; - public static final float ANGULAR_VMAX = 45f; - public static final float ANGULAR_VMIN = 0f; - public static final float SCALE_MIN = 0.5f; - public static final float SCALE_MAX = 4f; - - public float v, vr; - - public final float[] hsv = new float[3]; - - public float angle, anglex, angley; - public float fuse; - public float dist; - public float endscale; - public float boardCenterX, boardCenterY; - - public ComponentName component; - - public FlyingIcon(Context context, AttributeSet as) { - super(context, as); - setLayerType(View.LAYER_TYPE_HARDWARE, null); - - setBackgroundResource(R.drawable.flying_icon_bg); - //android.util.Log.d("RocketLauncher", "ctor: " + this); - hsv[1] = 1f; - hsv[2] = 1f; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!mManeuveringThrusters || component == null) { - return false; - } - if (getAlpha() < 0.5f) { - setPressed(false); - return false; - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - setPressed(true); - Board.this.resetWarpTimer(); - break; - case MotionEvent.ACTION_MOVE: - final Rect hit = new Rect(); - final Point offset = new Point(); - getGlobalVisibleRect(hit, offset); - final int globx = (int) event.getX() + offset.x; - final int globy = (int) event.getY() + offset.y; - setPressed(hit.contains(globx, globy)); - Board.this.resetWarpTimer(); - break; - case MotionEvent.ACTION_UP: - if (isPressed()) { - setPressed(false); - postDelayed(new Runnable() { - public void run() { - try { - getContext().startActivity(new Intent(Intent.ACTION_MAIN) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .setComponent(component)); - } catch (android.content.ActivityNotFoundException e) { - } catch (SecurityException e) { - } - } - }, LAUNCH_ZOOM_TIME); - endscale = 0; - AnimatorSet s = new AnimatorSet(); - s.playTogether( - ObjectAnimator.ofFloat(this, "scaleX", 15f), - ObjectAnimator.ofFloat(this, "scaleY", 15f), - ObjectAnimator.ofFloat(this, "alpha", 0f) - ); - - // make sure things are still moving until the very last instant the - // activity is visible - s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25)); - s.setInterpolator(new android.view.animation.AccelerateInterpolator(3)); - s.start(); - } - break; - } - return true; - } - - public String toString() { - return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>", - "icon", getX(), getY(), v, angle, dist, fuse); - } - - public void randomizeIcon() { - component = pick(mComponentNames); - setImageBitmap(mIcons.get(component)); - } - - public void randomize() { - v = randfrange(VMIN, VMAX); - angle = randfrange(0, 360f); - anglex = (float) Math.sin(angle / 180. * Math.PI); - angley = (float) Math.cos(angle / 180. * Math.PI); - vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign(); - endscale = randfrange(SCALE_MIN, SCALE_MAX); - - randomizeIcon(); - } - public void reset() { - randomize(); - boardCenterX = (Board.this.getWidth() - getWidth()) / 2; - boardCenterY = (Board.this.getHeight() - getHeight()) / 2; - setX(boardCenterX); - setY(boardCenterY); - fuse = (float) Math.max(boardCenterX, boardCenterY); - setRotation(180-angle); - setScaleX(0f); - setScaleY(0f); - dist = 0; - setAlpha(0f); - } - public void update(float dt) { - dist += v * dt; - setX(getX() + anglex * v * dt); - setY(getY() + angley * v * dt); - //setRotation(getRotation() + vr * dt); - if (endscale > 0) { - float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse)); - setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3))); - setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3))); - final float q1 = fuse*0.15f; - final float q4 = fuse*0.75f; - if (dist < q1) { - setAlpha((float) Math.sqrt(dist/q1)); - } else if (dist > q4) { - setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2))); - } else { - setAlpha(1f); - } - } - } - } - - public class FlyingStar extends FlyingIcon { - public FlyingStar(Context context, AttributeSet as) { - super(context, as); - } - public void randomizeIcon() { - setImageResource(R.drawable.widget_resize_handle_bottom); - } - public void randomize() { - super.randomize(); - v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate - endscale = randfrange(1f, 2f); // ensmallen - } - } - - TimeAnimator mAnim; - - public Board(Context context, AttributeSet as) { - super(context, as); - - setBackgroundColor(0xFF000000); - - LauncherApplication app = (LauncherApplication)context.getApplicationContext(); - mIcons = app.getIconCache().getAllIcons(); - mComponentNames = new ComponentName[mIcons.size()]; - mComponentNames = mIcons.keySet().toArray(mComponentNames); - } - - private void reset() { - removeAllViews(); - - final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - - if (FIXED_STARS) { - for(int i=0; i<20; i++) { - ImageView fixedStar = new ImageView(getContext(), null); - fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom); - final float s = randfrange(0.25f, 0.75f); - fixedStar.setScaleX(s); - fixedStar.setScaleY(s); - fixedStar.setAlpha(0.75f); - addView(fixedStar, wrap); - fixedStar.setX(randfrange(0, getWidth())); - fixedStar.setY(randfrange(0, getHeight())); - } - } - - for(int i=0; i MANEUVERING_THRUST_SCALE) { - mSpeedScale -= (2*deltaTime/1000f); - } - if (mSpeedScale < MANEUVERING_THRUST_SCALE) { - mSpeedScale = MANEUVERING_THRUST_SCALE; - } - } else { - if (mSpeedScale < 1.0f) { - mSpeedScale += (deltaTime/1000f); - } - if (mSpeedScale > 1.0f) { - mSpeedScale = 1.0f; - } - } - - for (int i=0; i getWidth() - || nv.getY() + scaledHeight < 0 - || nv.getY() - scaledHeight > getHeight()) - { - nv.reset(); - } - } - } - }); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - setLayerType(View.LAYER_TYPE_HARDWARE, null); - setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); - - reset(); - mAnim.start(); - } - - protected void onSizeChanged (int w, int h, int oldw, int oldh) { - super.onSizeChanged(w,h,oldw,oldh); - mAnim.cancel(); - reset(); - mAnim.start(); - } - - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mAnim.cancel(); - } - - @Override - public boolean isOpaque() { - return true; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - // we want to eat touch events ourselves if we're in warp speed - return (!(ROCKET_LAUNCHER && mManeuveringThrusters)); - } - - final Runnable mEngageWarp = new Runnable() { - @Override - public void run() { - mManeuveringThrusters = false; - } - }; - public void resetWarpTimer() { - final Handler h = getHandler(); - h.removeCallbacks(mEngageWarp); - h.postDelayed(mEngageWarp, 5000); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!ROCKET_LAUNCHER) { - return true; - } - - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (!mManeuveringThrusters) { - mManeuveringThrusters = true; - resetWarpTimer(); - return true; - } - } - - return false; - } - } - - @Override - public void onStart() { - super.onStart(); - - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - final int longside = metrics.widthPixels > metrics.heightPixels - ? metrics.widthPixels : metrics.heightPixels; - - Board b = new Board(this, null); - setContentView(b, new ViewGroup.LayoutParams(longside, longside)); - b.setX((metrics.widthPixels - longside) / 2); - b.setY((metrics.heightPixels - longside) / 2); - } - - @Override - public void onUserInteraction() { - if (!ROCKET_LAUNCHER) { - finish(); - } - } -} diff --git a/src/com/android/launcher2/SearchDropTargetBar.java b/src/com/android/launcher2/SearchDropTargetBar.java deleted file mode 100644 index a1d36cdfa..000000000 --- a/src/com/android/launcher2/SearchDropTargetBar.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.animation.AccelerateInterpolator; -import android.widget.FrameLayout; - -import com.android.launcher.R; - -/* - * Ths bar will manage the transition between the QSB search bar and the delete drop - * targets so that each of the individual IconDropTargets don't have to. - */ -public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener { - - private static final int sTransitionInDuration = 200; - private static final int sTransitionOutDuration = 175; - - private ObjectAnimator mDropTargetBarAnim; - private ObjectAnimator mQSBSearchBarAnim; - private static final AccelerateInterpolator sAccelerateInterpolator = - new AccelerateInterpolator(); - - private boolean mIsSearchBarHidden; - private View mQSBSearchBar; - private View mDropTargetBar; - private ButtonDropTarget mInfoDropTarget; - private ButtonDropTarget mDeleteDropTarget; - private int mBarHeight; - private boolean mDeferOnDragEnd = false; - - private Drawable mPreviousBackground; - private boolean mEnableDropDownDropTargets; - - public SearchDropTargetBar(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchDropTargetBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void setup(Launcher launcher, DragController dragController) { - dragController.addDragListener(this); - dragController.addDragListener(mInfoDropTarget); - dragController.addDragListener(mDeleteDropTarget); - dragController.addDropTarget(mInfoDropTarget); - dragController.addDropTarget(mDeleteDropTarget); - dragController.setFlingToDeleteDropTarget(mDeleteDropTarget); - mInfoDropTarget.setLauncher(launcher); - mDeleteDropTarget.setLauncher(launcher); - } - - private void prepareStartAnimation(View v) { - // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd - // callback below) - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - v.buildLayer(); - } - - private void setupAnimation(ObjectAnimator anim, final View v) { - anim.setInterpolator(sAccelerateInterpolator); - anim.setDuration(sTransitionInDuration); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - v.setLayerType(View.LAYER_TYPE_NONE, null); - } - }); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - // Get the individual components - mQSBSearchBar = findViewById(R.id.qsb_search_bar); - mDropTargetBar = findViewById(R.id.drag_target_bar); - mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text); - mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text); - mBarHeight = getResources().getDimensionPixelSize(R.dimen.qsb_bar_height); - - mInfoDropTarget.setSearchDropTargetBar(this); - mDeleteDropTarget.setSearchDropTargetBar(this); - - mEnableDropDownDropTargets = - getResources().getBoolean(R.bool.config_useDropTargetDownTransition); - - // Create the various fade animations - if (mEnableDropDownDropTargets) { - mDropTargetBar.setTranslationY(-mBarHeight); - mDropTargetBarAnim = ObjectAnimator.ofFloat(mDropTargetBar, "translationY", - -mBarHeight, 0f); - mQSBSearchBarAnim = ObjectAnimator.ofFloat(mQSBSearchBar, "translationY", 0, - -mBarHeight); - } else { - mDropTargetBar.setAlpha(0f); - mDropTargetBarAnim = ObjectAnimator.ofFloat(mDropTargetBar, "alpha", 0f, 1f); - mQSBSearchBarAnim = ObjectAnimator.ofFloat(mQSBSearchBar, "alpha", 1f, 0f); - } - setupAnimation(mDropTargetBarAnim, mDropTargetBar); - setupAnimation(mQSBSearchBarAnim, mQSBSearchBar); - } - - public void finishAnimations() { - prepareStartAnimation(mDropTargetBar); - mDropTargetBarAnim.reverse(); - prepareStartAnimation(mQSBSearchBar); - mQSBSearchBarAnim.reverse(); - } - - /* - * Shows and hides the search bar. - */ - public void showSearchBar(boolean animated) { - if (!mIsSearchBarHidden) return; - if (animated) { - prepareStartAnimation(mQSBSearchBar); - mQSBSearchBarAnim.reverse(); - } else { - mQSBSearchBarAnim.cancel(); - if (mEnableDropDownDropTargets) { - mQSBSearchBar.setTranslationY(0); - } else { - mQSBSearchBar.setAlpha(1f); - } - } - mIsSearchBarHidden = false; - } - public void hideSearchBar(boolean animated) { - if (mIsSearchBarHidden) return; - if (animated) { - prepareStartAnimation(mQSBSearchBar); - mQSBSearchBarAnim.start(); - } else { - mQSBSearchBarAnim.cancel(); - if (mEnableDropDownDropTargets) { - mQSBSearchBar.setTranslationY(-mBarHeight); - } else { - mQSBSearchBar.setAlpha(0f); - } - } - mIsSearchBarHidden = true; - } - - /* - * Gets various transition durations. - */ - public int getTransitionInDuration() { - return sTransitionInDuration; - } - public int getTransitionOutDuration() { - return sTransitionOutDuration; - } - - /* - * DragController.DragListener implementation - */ - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - // Animate out the QSB search bar, and animate in the drop target bar - prepareStartAnimation(mDropTargetBar); - mDropTargetBarAnim.start(); - if (!mIsSearchBarHidden) { - prepareStartAnimation(mQSBSearchBar); - mQSBSearchBarAnim.start(); - } - } - - public void deferOnDragEnd() { - mDeferOnDragEnd = true; - } - - @Override - public void onDragEnd() { - if (!mDeferOnDragEnd) { - // Restore the QSB search bar, and animate out the drop target bar - prepareStartAnimation(mDropTargetBar); - mDropTargetBarAnim.reverse(); - if (!mIsSearchBarHidden) { - prepareStartAnimation(mQSBSearchBar); - mQSBSearchBarAnim.reverse(); - } - } else { - mDeferOnDragEnd = false; - } - } - - public void onSearchPackagesChanged(boolean searchVisible, boolean voiceVisible) { - if (mQSBSearchBar != null) { - Drawable bg = mQSBSearchBar.getBackground(); - if (bg != null && (!searchVisible && !voiceVisible)) { - // Save the background and disable it - mPreviousBackground = bg; - mQSBSearchBar.setBackgroundResource(0); - } else if (mPreviousBackground != null && (searchVisible || voiceVisible)) { - // Restore the background - mQSBSearchBar.setBackground(mPreviousBackground); - } - } - } - - public Rect getSearchBarBounds() { - if (mQSBSearchBar != null) { - final float appScale = mQSBSearchBar.getContext().getResources() - .getCompatibilityInfo().applicationScale; - final int[] pos = new int[2]; - mQSBSearchBar.getLocationOnScreen(pos); - - final Rect rect = new Rect(); - rect.left = (int) (pos[0] * appScale + 0.5f); - rect.top = (int) (pos[1] * appScale + 0.5f); - rect.right = (int) ((pos[0] + mQSBSearchBar.getWidth()) * appScale + 0.5f); - rect.bottom = (int) ((pos[1] + mQSBSearchBar.getHeight()) * appScale + 0.5f); - return rect; - } else { - return null; - } - } -} diff --git a/src/com/android/launcher2/ShortcutAndWidgetContainer.java b/src/com/android/launcher2/ShortcutAndWidgetContainer.java deleted file mode 100644 index 8bebdcd45..000000000 --- a/src/com/android/launcher2/ShortcutAndWidgetContainer.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.app.WallpaperManager; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.view.View; -import android.view.ViewGroup; - -public class ShortcutAndWidgetContainer extends ViewGroup { - static final String TAG = "CellLayoutChildren"; - - // 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 WallpaperManager mWallpaperManager; - - private int mCellWidth; - private int mCellHeight; - - private int mWidthGap; - private int mHeightGap; - - public ShortcutAndWidgetContainer(Context context) { - super(context); - mWallpaperManager = WallpaperManager.getInstance(context); - } - - public void enableHardwareLayers() { - setLayerType(LAYER_TYPE_HARDWARE, null); - } - - public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap ) { - mCellWidth = cellWidth; - mCellHeight = cellHeight; - mWidthGap = widthGap; - mHeightGap = heightGap; - } - - public View getChildAt(int x, int y) { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) && - (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) { - return child; - } - } - return null; - } - - @Override - protected void dispatchDraw(Canvas canvas) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - // Debug drawing for hit space - Paint p = new Paint(); - p.setColor(0x6600FF00); - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); - final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p); - } - } - super.dispatchDraw(canvas); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - measureChild(child); - } - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(widthSpecSize, heightSpecSize); - } - - public void setupLp(CellLayout.LayoutParams lp) { - lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap); - } - - public void measureChild(View child) { - final int cellWidth = mCellWidth; - final int cellHeight = mCellHeight; - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); - int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, - MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - int childLeft = lp.x; - int childTop = lp.y; - child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); - - if (lp.dropped) { - lp.dropped = false; - - final int[] cellXY = mTmpCellXY; - getLocationOnScreen(cellXY); - mWallpaperManager.sendWallpaperCommand(getWindowToken(), - WallpaperManager.COMMAND_DROP, - cellXY[0] + childLeft + lp.width / 2, - cellXY[1] + childTop + lp.height / 2, 0, null); - } - } - } - } - - @Override - public boolean shouldDelayChildPressedState() { - 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 - 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(); - } - } - - @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() && enabled) { - view.buildDrawingCache(true); - } - } - } - - @Override - protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { - super.setChildrenDrawnWithCacheEnabled(enabled); - } -} diff --git a/src/com/android/launcher2/ShortcutInfo.java b/src/com/android/launcher2/ShortcutInfo.java deleted file mode 100644 index 533059f57..000000000 --- a/src/com/android/launcher2/ShortcutInfo.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import java.util.ArrayList; - -import android.content.ComponentName; -import android.content.ContentValues; -import android.content.Intent; -import android.graphics.Bitmap; -import android.util.Log; - -/** - * Represents a launchable icon on the workspaces and in folders. - */ -class ShortcutInfo extends ItemInfo { - - /** - * The application name. - */ - CharSequence title; - - /** - * The intent used to start the application. - */ - Intent intent; - - /** - * Indicates whether the icon comes from an application's resource (if false) - * or from a custom Bitmap (if true.) - */ - boolean customIcon; - - /** - * Indicates whether we're using the default fallback icon instead of something from the - * app. - */ - boolean usingFallbackIcon; - - /** - * If isShortcut=true and customIcon=false, this contains a reference to the - * shortcut icon as an application's resource. - */ - Intent.ShortcutIconResource iconResource; - - /** - * The application icon. - */ - private Bitmap mIcon; - - ShortcutInfo() { - itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; - } - - public ShortcutInfo(ShortcutInfo info) { - super(info); - title = info.title.toString(); - intent = new Intent(info.intent); - if (info.iconResource != null) { - iconResource = new Intent.ShortcutIconResource(); - iconResource.packageName = info.iconResource.packageName; - iconResource.resourceName = info.iconResource.resourceName; - } - mIcon = info.mIcon; // TODO: should make a copy here. maybe we don't need this ctor at all - customIcon = info.customIcon; - } - - /** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */ - public ShortcutInfo(ApplicationInfo info) { - super(info); - title = info.title.toString(); - intent = new Intent(info.intent); - customIcon = false; - } - - public void setIcon(Bitmap b) { - mIcon = b; - } - - public Bitmap getIcon(IconCache iconCache) { - if (mIcon == null) { - updateIcon(iconCache); - } - return mIcon; - } - - /** Returns the package name that the shortcut's intent will resolve to, or an empty string if - * none exists. */ - String getPackageName() { - return super.getPackageName(intent); - } - - public void updateIcon(IconCache iconCache) { - mIcon = iconCache.getIcon(intent); - usingFallbackIcon = iconCache.isDefaultIcon(mIcon); - } - - /** - * Creates the application intent based on a component name and various launch flags. - * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}. - * - * @param className the class name of the component representing the intent - * @param launchFlags the launch flags - */ - final void setActivity(ComponentName className, int launchFlags) { - intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(className); - intent.setFlags(launchFlags); - itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION; - } - - @Override - void onAddToDatabase(ContentValues values) { - super.onAddToDatabase(values); - - String titleStr = title != null ? title.toString() : null; - values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr); - - String uri = intent != null ? intent.toUri(0) : null; - values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri); - - if (customIcon) { - values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, - LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP); - writeBitmap(values, mIcon); - } else { - if (!usingFallbackIcon) { - writeBitmap(values, mIcon); - } - values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, - LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE); - if (iconResource != null) { - values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, - iconResource.packageName); - values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE, - iconResource.resourceName); - } - } - } - - @Override - public String toString() { - return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id - + " type=" + this.itemType + " container=" + this.container + " screen=" + screen - + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY - + " isGesture=" + isGesture + " dropPos=" + dropPos + ")"; - } - - public static void dumpShortcutInfoList(String tag, String label, - ArrayList list) { - Log.d(tag, label + " size=" + list.size()); - for (ShortcutInfo info: list) { - Log.d(tag, " title=\"" + info.title + " icon=" + info.mIcon - + " customIcon=" + info.customIcon); - } - } -} - diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java deleted file mode 100644 index 7e47f1a3f..000000000 --- a/src/com/android/launcher2/SmoothPagedView.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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 DEFAULT_MODE = 0; - static final int X_LARGE_MODE = 1; - - int mScrollMode; - - private Interpolator mScrollInterpolator; - - public static class OvershootInterpolator implements Interpolator { - private static final float DEFAULT_TENSION = 1.3f; - private float mTension; - - public OvershootInterpolator() { - 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 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), we only do this in the OVERSHOOT_MODE, ie. on phones - mDeferScrollUpdate = mScrollMode != X_LARGE_MODE; - } - - protected int getScrollMode() { - return X_LARGE_MODE; - } - - /** - * Initializes various states for this workspace. - */ - @Override - protected void init() { - super.init(); - - mScrollMode = getScrollMode(); - if (mScrollMode == DEFAULT_MODE) { - mBaseLineFlingVelocity = 2500.0f; - mFlingVelocityInfluence = 0.4f; - mScrollInterpolator = new OvershootInterpolator(); - mScroller = new Scroller(getContext(), mScrollInterpolator); - } - } - - @Override - protected void snapToDestination() { - if (mScrollMode == X_LARGE_MODE) { - super.snapToDestination(); - } else { - snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0); - } - } - - @Override - protected void snapToPageWithVelocity(int whichPage, int velocity) { - if (mScrollMode == X_LARGE_MODE) { - super.snapToPageWithVelocity(whichPage, velocity); - } else { - snapToPageWithVelocity(whichPage, 0, true); - } - } - - private 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 - mUnboundedScrollX; - int duration = (screenDelta + 1) * 100; - - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } - - if (settle) { - ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta); - } else { - ((OvershootInterpolator) 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) { - if (mScrollMode == X_LARGE_MODE) { - super.snapToPage(whichPage); - } else { - snapToPageWithVelocity(whichPage, 0, false); - } - } - - @Override - public void computeScroll() { - if (mScrollMode == X_LARGE_MODE) { - super.computeScroll(); - } else { - 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 - mUnboundedScrollX; - scrollTo(Math.round(mUnboundedScrollX + dx * e), getScrollY()); - 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/SpringLoadedDragController.java b/src/com/android/launcher2/SpringLoadedDragController.java deleted file mode 100644 index d96aab794..000000000 --- a/src/com/android/launcher2/SpringLoadedDragController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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; - -public class SpringLoadedDragController implements OnAlarmListener { - // how long the user must hover over a mini-screen before it unshrinks - final long ENTER_SPRING_LOAD_HOVER_TIME = 500; - final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950; - final long EXIT_SPRING_LOAD_HOVER_TIME = 200; - - Alarm mAlarm; - - // the screen the user is currently hovering over, if any - private CellLayout mScreen; - private Launcher mLauncher; - - public SpringLoadedDragController(Launcher launcher) { - mLauncher = launcher; - mAlarm = new Alarm(); - mAlarm.setOnAlarmListener(this); - } - - public void cancel() { - mAlarm.cancelAlarm(); - } - - // Set a new alarm to expire for the screen that we are hovering over now - public void setAlarm(CellLayout cl) { - mAlarm.cancelAlarm(); - mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME : - ENTER_SPRING_LOAD_HOVER_TIME); - mScreen = cl; - } - - // this is called when our timer runs out - public void onAlarm(Alarm alarm) { - if (mScreen != null) { - // Snap to the screen that we are hovering over now - Workspace w = mLauncher.getWorkspace(); - int page = w.indexOfChild(mScreen); - if (page != w.getCurrentPage()) { - w.snapToPage(page); - } - } else { - mLauncher.getDragController().cancelDrag(); - } - } -} diff --git a/src/com/android/launcher2/StrokedTextView.java b/src/com/android/launcher2/StrokedTextView.java deleted file mode 100644 index 4e28d17d7..000000000 --- a/src/com/android/launcher2/StrokedTextView.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.launcher.R; - -/** - * This class adds a stroke to the generic TextView allowing the text to stand out better against - * the background (ie. in the AllApps button). - */ -public class StrokedTextView extends TextView { - private final Canvas mCanvas = new Canvas(); - private final Paint mPaint = new Paint(); - private Bitmap mCache; - private boolean mUpdateCachedBitmap; - private int mStrokeColor; - private float mStrokeWidth; - private int mTextColor; - - public StrokedTextView(Context context) { - super(context); - init(context, null, 0); - } - - public StrokedTextView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - public StrokedTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs, defStyle); - } - - private void init(Context context, AttributeSet attrs, int defStyle) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StrokedTextView, - defStyle, 0); - mStrokeColor = a.getColor(R.styleable.StrokedTextView_strokeColor, 0xFF000000); - mStrokeWidth = a.getFloat(R.styleable.StrokedTextView_strokeWidth, 0.0f); - mTextColor = a.getColor(R.styleable.StrokedTextView_strokeTextColor, 0xFFFFFFFF); - a.recycle(); - mUpdateCachedBitmap = true; - - // Setup the text paint - mPaint.setAntiAlias(true); - mPaint.setStyle(Paint.Style.FILL_AND_STROKE); - } - - protected void onTextChanged(CharSequence text, int start, int before, int after) { - super.onTextChanged(text, start, before, after); - mUpdateCachedBitmap = true; - } - - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (w > 0 && h > 0) { - mUpdateCachedBitmap = true; - mCache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - } else { - mCache = null; - } - } - - protected void onDraw(Canvas canvas) { - if (mCache != null) { - if (mUpdateCachedBitmap) { - final int w = getMeasuredWidth(); - final int h = getMeasuredHeight(); - final String text = getText().toString(); - final Rect textBounds = new Rect(); - final Paint textPaint = getPaint(); - final int textWidth = (int) textPaint.measureText(text); - textPaint.getTextBounds("x", 0, 1, textBounds); - - // Clear the old cached image - mCanvas.setBitmap(mCache); - mCanvas.drawColor(0, PorterDuff.Mode.CLEAR); - - // Draw the drawable - final int drawableLeft = getPaddingLeft(); - final int drawableTop = getPaddingTop(); - final Drawable[] drawables = getCompoundDrawables(); - for (int i = 0; i < drawables.length; ++i) { - if (drawables[i] != null) { - drawables[i].setBounds(drawableLeft, drawableTop, - drawableLeft + drawables[i].getIntrinsicWidth(), - drawableTop + drawables[i].getIntrinsicHeight()); - drawables[i].draw(mCanvas); - } - } - - final int left = w - getPaddingRight() - textWidth; - final int bottom = (h + textBounds.height()) / 2; - - // Draw the outline of the text - mPaint.setStrokeWidth(mStrokeWidth); - mPaint.setColor(mStrokeColor); - mPaint.setTextSize(getTextSize()); - mCanvas.drawText(text, left, bottom, mPaint); - - // Draw the text itself - mPaint.setStrokeWidth(0); - mPaint.setColor(mTextColor); - mCanvas.drawText(text, left, bottom, mPaint); - - mUpdateCachedBitmap = false; - } - canvas.drawBitmap(mCache, 0, 0, mPaint); - } else { - super.onDraw(canvas); - } - } -} diff --git a/src/com/android/launcher2/SymmetricalLinearTween.java b/src/com/android/launcher2/SymmetricalLinearTween.java deleted file mode 100644 index da02242c8..000000000 --- a/src/com/android/launcher2/SymmetricalLinearTween.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.os.Handler; -import android.os.SystemClock; - -/** - * Provides an animation between 0.0f and 1.0f over a given duration. - */ -class SymmetricalLinearTween { - - private static final int FPS = 30; - private static final int FRAME_TIME = 1000 / FPS; - - Handler mHandler; - int mDuration; - TweenCallback mCallback; - - boolean mRunning; - long mBase; - boolean mDirection; - float mValue; - - /** - * @param duration milliseconds duration - * @param callback callbacks - */ - public SymmetricalLinearTween(boolean initial, int duration, TweenCallback callback) { - mValue = initial ? 1.0f : 0.0f; - mDirection = initial; - mDuration = duration; - mCallback = callback; - mHandler = new Handler(); - } - - /** - * Starts the tweening. - * - * @param direction If direction is true, the value goes towards 1.0f. If direction - * is false, the value goes towards 0.0f. - */ - public void start(boolean direction) { - start(direction, SystemClock.uptimeMillis()); - } - - /** - * Starts the tweening. - * - * @param direction If direction is true, the value goes towards 1.0f. If direction - * is false, the value goes towards 0.0f. - * @param baseTime The time to use as zero for this animation, in the - * {@link SystemClock.uptimeMillis} time base. This allows you to - * synchronize multiple animations. - */ - public void start(boolean direction, long baseTime) { - if (direction != mDirection) { - if (!mRunning) { - mBase = baseTime; - mRunning = true; - mCallback.onTweenStarted(); - long next = SystemClock.uptimeMillis() + FRAME_TIME; - mHandler.postAtTime(mTick, next); - } else { - // reverse direction - long now = SystemClock.uptimeMillis(); - long diff = now - mBase; - mBase = now + diff - mDuration; - } - mDirection = direction; - } - } - - Runnable mTick = new Runnable() { - public void run() { - long base = mBase; - long now = SystemClock.uptimeMillis(); - long diff = now-base; - int duration = mDuration; - float val = diff/(float)duration; - if (!mDirection) { - val = 1.0f - val; - } - if (val > 1.0f) { - val = 1.0f; - } else if (val < 0.0f) { - val = 0.0f; - } - float old = mValue; - mValue = val; - mCallback.onTweenValueChanged(val, old); - int frame = (int)(diff / FRAME_TIME); - long next = base + ((frame+1)*FRAME_TIME); - if (diff < duration) { - mHandler.postAtTime(this, next); - } - if (diff >= duration) { - mCallback.onTweenFinished(); - mRunning = false; - } - } - }; -} - diff --git a/src/com/android/launcher2/TweenCallback.java b/src/com/android/launcher2/TweenCallback.java deleted file mode 100644 index 380a21774..000000000 --- a/src/com/android/launcher2/TweenCallback.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -interface TweenCallback { - void onTweenValueChanged(float value, float oldValue); - void onTweenStarted(); - void onTweenFinished(); -} - diff --git a/src/com/android/launcher2/UninstallShortcutReceiver.java b/src/com/android/launcher2/UninstallShortcutReceiver.java deleted file mode 100644 index 02590c9f6..000000000 --- a/src/com/android/launcher2/UninstallShortcutReceiver.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.widget.Toast; - -import com.android.launcher.R; - -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -public class UninstallShortcutReceiver extends BroadcastReceiver { - private static final String ACTION_UNINSTALL_SHORTCUT = - "com.android.launcher.action.UNINSTALL_SHORTCUT"; - - // The set of shortcuts that are pending uninstall - private static ArrayList mUninstallQueue = - new ArrayList(); - - // Determines whether to defer uninstalling shortcuts immediately until - // disableAndFlushUninstallQueue() is called. - private static boolean mUseUninstallQueue = false; - - private static class PendingUninstallShortcutInfo { - Intent data; - - public PendingUninstallShortcutInfo(Intent rawData) { - data = rawData; - } - } - - public void onReceive(Context context, Intent data) { - if (!ACTION_UNINSTALL_SHORTCUT.equals(data.getAction())) { - return; - } - - PendingUninstallShortcutInfo info = new PendingUninstallShortcutInfo(data); - if (mUseUninstallQueue) { - mUninstallQueue.add(info); - } else { - processUninstallShortcut(context, info); - } - } - - static void enableUninstallQueue() { - mUseUninstallQueue = true; - } - - static void disableAndFlushUninstallQueue(Context context) { - mUseUninstallQueue = false; - Iterator iter = mUninstallQueue.iterator(); - while (iter.hasNext()) { - processUninstallShortcut(context, iter.next()); - iter.remove(); - } - } - - private static void processUninstallShortcut(Context context, - PendingUninstallShortcutInfo pendingInfo) { - String spKey = LauncherApplication.getSharedPreferencesKey(); - SharedPreferences sharedPrefs = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); - - final Intent data = pendingInfo.data; - - LauncherApplication app = (LauncherApplication) context.getApplicationContext(); - synchronized (app) { - removeShortcut(context, data, sharedPrefs); - } - } - - private static void removeShortcut(Context context, Intent data, - final SharedPreferences sharedPrefs) { - Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); - String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true); - - if (intent != null && name != null) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT }, - LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null); - - final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); - - boolean changed = false; - - try { - while (c.moveToNext()) { - try { - if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) { - final long id = c.getLong(idIndex); - final Uri uri = LauncherSettings.Favorites.getContentUri(id, false); - cr.delete(uri, null, null); - changed = true; - if (!duplicate) { - break; - } - } - } catch (URISyntaxException e) { - // Ignore - } - } - } finally { - c.close(); - } - - if (changed) { - cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null); - Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name), - Toast.LENGTH_SHORT).show(); - } - - // Remove any items due to be animated - boolean appRemoved; - Set newApps = new HashSet(); - newApps = sharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps); - synchronized (newApps) { - do { - appRemoved = newApps.remove(intent.toUri(0).toString()); - } while (appRemoved); - } - if (appRemoved) { - final Set savedNewApps = newApps; - new Thread("setNewAppsThread-remove") { - public void run() { - synchronized (savedNewApps) { - SharedPreferences.Editor editor = sharedPrefs.edit(); - editor.putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, - savedNewApps); - if (savedNewApps.isEmpty()) { - // Reset the page index if there are no more items - editor.putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1); - } - editor.commit(); - } - } - }.start(); - } - } - } -} diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java deleted file mode 100644 index b27f7bb11..000000000 --- a/src/com/android/launcher2/Utilities.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import java.util.Random; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BlurMaskFilter; -import android.graphics.Canvas; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.TableMaskFilter; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.PaintDrawable; -import android.util.DisplayMetrics; - -import com.android.launcher.R; - -/** - * Various utilities shared amongst the Launcher's classes. - */ -final class Utilities { - @SuppressWarnings("unused") - private static final String TAG = "Launcher.Utilities"; - - private static int sIconWidth = -1; - private static int sIconHeight = -1; - private static int sIconTextureWidth = -1; - private static int sIconTextureHeight = -1; - - private static final Paint sBlurPaint = new Paint(); - private static final Paint sGlowColorPressedPaint = new Paint(); - private static final Paint sGlowColorFocusedPaint = new Paint(); - private static final Paint sDisabledPaint = new Paint(); - private static final Rect sOldBounds = new Rect(); - private static final Canvas sCanvas = new Canvas(); - - static { - sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, - Paint.FILTER_BITMAP_FLAG)); - } - static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff }; - static int sColorIndex = 0; - - /** - * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS - * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size) - * to the proper size (48dp) - */ - static Bitmap createIconBitmap(Bitmap icon, Context context) { - int textureWidth = sIconTextureWidth; - int textureHeight = sIconTextureHeight; - int sourceWidth = icon.getWidth(); - int sourceHeight = icon.getHeight(); - if (sourceWidth > textureWidth && sourceHeight > textureHeight) { - // Icon is bigger than it should be; clip it (solves the GB->ICS migration case) - return Bitmap.createBitmap(icon, - (sourceWidth - textureWidth) / 2, - (sourceHeight - textureHeight) / 2, - textureWidth, textureHeight); - } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) { - // Icon is the right size, no need to change it - return icon; - } else { - // Icon is too small, render to a larger bitmap - final Resources resources = context.getResources(); - return createIconBitmap(new BitmapDrawable(resources, icon), context); - } - } - - /** - * Returns a bitmap suitable for the all apps view. - */ - static Bitmap createIconBitmap(Drawable icon, Context context) { - synchronized (sCanvas) { // we share the statics :-( - if (sIconWidth == -1) { - initStatics(context); - } - - int width = sIconWidth; - int height = sIconHeight; - - if (icon instanceof PaintDrawable) { - PaintDrawable painter = (PaintDrawable) icon; - painter.setIntrinsicWidth(width); - painter.setIntrinsicHeight(height); - } else if (icon instanceof BitmapDrawable) { - // Ensure the bitmap has a density. - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); - } - } - int sourceWidth = icon.getIntrinsicWidth(); - int sourceHeight = icon.getIntrinsicHeight(); - if (sourceWidth > 0 && sourceHeight > 0) { - // There are intrinsic sizes. - if (width < sourceWidth || height < sourceHeight) { - // It's too big, scale it down. - final float ratio = (float) sourceWidth / sourceHeight; - if (sourceWidth > sourceHeight) { - height = (int) (width / ratio); - } else if (sourceHeight > sourceWidth) { - width = (int) (height * ratio); - } - } else if (sourceWidth < width && sourceHeight < height) { - // Don't scale up the icon - width = sourceWidth; - height = sourceHeight; - } - } - - // no intrinsic size --> use default size - int textureWidth = sIconTextureWidth; - int textureHeight = sIconTextureHeight; - - final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, - Bitmap.Config.ARGB_8888); - final Canvas canvas = sCanvas; - canvas.setBitmap(bitmap); - - final int left = (textureWidth-width) / 2; - final int top = (textureHeight-height) / 2; - - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - // draw a big box for the icon for debugging - canvas.drawColor(sColors[sColorIndex]); - if (++sColorIndex >= sColors.length) sColorIndex = 0; - Paint debugPaint = new Paint(); - debugPaint.setColor(0xffcccc00); - canvas.drawRect(left, top, left+width, top+height, debugPaint); - } - - sOldBounds.set(icon.getBounds()); - icon.setBounds(left, top, left+width, top+height); - icon.draw(canvas); - icon.setBounds(sOldBounds); - canvas.setBitmap(null); - - return bitmap; - } - } - - static void drawSelectedAllAppsBitmap(Canvas dest, int destWidth, int destHeight, - boolean pressed, Bitmap src) { - synchronized (sCanvas) { // we share the statics :-( - if (sIconWidth == -1) { - // We can't have gotten to here without src being initialized, which - // comes from this file already. So just assert. - //initStatics(context); - throw new RuntimeException("Assertion failed: Utilities not initialized"); - } - - dest.drawColor(0, PorterDuff.Mode.CLEAR); - - int[] xy = new int[2]; - Bitmap mask = src.extractAlpha(sBlurPaint, xy); - - float px = (destWidth - src.getWidth()) / 2; - float py = (destHeight - src.getHeight()) / 2; - dest.drawBitmap(mask, px + xy[0], py + xy[1], - pressed ? sGlowColorPressedPaint : sGlowColorFocusedPaint); - - mask.recycle(); - } - } - - /** - * Returns a Bitmap representing the thumbnail of the specified Bitmap. - * The size of the thumbnail is defined by the dimension - * android.R.dimen.launcher_application_icon_size. - * - * @param bitmap The bitmap to get a thumbnail of. - * @param context The application's context. - * - * @return A thumbnail for the specified bitmap or the bitmap itself if the - * thumbnail could not be created. - */ - static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) { - synchronized (sCanvas) { // we share the statics :-( - if (sIconWidth == -1) { - initStatics(context); - } - - if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) { - return bitmap; - } else { - final Resources resources = context.getResources(); - return createIconBitmap(new BitmapDrawable(resources, bitmap), context); - } - } - } - - static Bitmap drawDisabledBitmap(Bitmap bitmap, Context context) { - synchronized (sCanvas) { // we share the statics :-( - if (sIconWidth == -1) { - initStatics(context); - } - final Bitmap disabled = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), - Bitmap.Config.ARGB_8888); - final Canvas canvas = sCanvas; - canvas.setBitmap(disabled); - - canvas.drawBitmap(bitmap, 0.0f, 0.0f, sDisabledPaint); - - canvas.setBitmap(null); - - return disabled; - } - } - - private static void initStatics(Context context) { - final Resources resources = context.getResources(); - final DisplayMetrics metrics = resources.getDisplayMetrics(); - final float density = metrics.density; - - sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size); - sIconTextureWidth = sIconTextureHeight = sIconWidth; - - sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL)); - sGlowColorPressedPaint.setColor(0xffffc300); - sGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); - sGlowColorFocusedPaint.setColor(0xffff8e00); - sGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); - - ColorMatrix cm = new ColorMatrix(); - cm.setSaturation(0.2f); - sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm)); - sDisabledPaint.setAlpha(0x88); - } - - /** Only works for positive numbers. */ - static int roundToPow2(int n) { - int orig = n; - n >>= 1; - int mask = 0x8000000; - while (mask != 0 && (n & mask) == 0) { - mask >>= 1; - } - while (mask != 0) { - n |= mask; - mask >>= 1; - } - n += 1; - if (n != orig) { - n <<= 1; - } - return n; - } - - static int generateRandomId() { - return new Random(System.currentTimeMillis()).nextInt(1 << 24); - } -} diff --git a/src/com/android/launcher2/WallpaperChooser.java b/src/com/android/launcher2/WallpaperChooser.java deleted file mode 100644 index 77e1e6ffb..000000000 --- a/src/com/android/launcher2/WallpaperChooser.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import com.android.launcher.R; - -import android.app.Activity; -import android.app.DialogFragment; -import android.app.Fragment; -import android.os.Bundle; - -public class WallpaperChooser extends Activity { - @SuppressWarnings("unused") - private static final String TAG = "Launcher.WallpaperChooser"; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - setContentView(R.layout.wallpaper_chooser_base); - - Fragment fragmentView = - getFragmentManager().findFragmentById(R.id.wallpaper_chooser_fragment); - // TODO: The following code is currently not exercised. Leaving it here in case it - // needs to be revived again. - if (fragmentView == null) { - /* When the screen is XLarge, the fragment is not included in the layout, so show it - * as a dialog - */ - DialogFragment fragment = WallpaperChooserDialogFragment.newInstance(); - fragment.show(getFragmentManager(), "dialog"); - } - } -} diff --git a/src/com/android/launcher2/WallpaperChooserDialogFragment.java b/src/com/android/launcher2/WallpaperChooserDialogFragment.java deleted file mode 100644 index b99d8ecb3..000000000 --- a/src/com/android/launcher2/WallpaperChooserDialogFragment.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * 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.app.Activity; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.WallpaperManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Gallery; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.SpinnerAdapter; - -import com.android.launcher.R; - -import java.io.IOException; -import java.util.ArrayList; - -public class WallpaperChooserDialogFragment extends DialogFragment implements - AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { - - private static final String TAG = "Launcher.WallpaperChooserDialogFragment"; - private static final String EMBEDDED_KEY = "com.android.launcher2." - + "WallpaperChooserDialogFragment.EMBEDDED_KEY"; - - private boolean mEmbedded; - private Bitmap mBitmap = null; - - private ArrayList mThumbs; - private ArrayList mImages; - private WallpaperLoader mLoader; - private WallpaperDrawable mWallpaperDrawable = new WallpaperDrawable(); - - public static WallpaperChooserDialogFragment newInstance() { - WallpaperChooserDialogFragment fragment = new WallpaperChooserDialogFragment(); - fragment.setCancelable(true); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null && savedInstanceState.containsKey(EMBEDDED_KEY)) { - mEmbedded = savedInstanceState.getBoolean(EMBEDDED_KEY); - } else { - mEmbedded = isInLayout(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - outState.putBoolean(EMBEDDED_KEY, mEmbedded); - } - - private void cancelLoader() { - if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) { - mLoader.cancel(true); - mLoader = null; - } - } - - @Override - public void onDetach() { - super.onDetach(); - - cancelLoader(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - cancelLoader(); - } - - @Override - public void onDismiss(DialogInterface dialog) { - super.onDismiss(dialog); - /* On orientation changes, the dialog is effectively "dismissed" so this is called - * when the activity is no longer associated with this dying dialog fragment. We - * should just safely ignore this case by checking if getActivity() returns null - */ - Activity activity = getActivity(); - if (activity != null) { - activity.finish(); - } - } - - /* This will only be called when in XLarge mode, since this Fragment is invoked like - * a dialog in that mode - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - findWallpapers(); - - return null; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - findWallpapers(); - - /* If this fragment is embedded in the layout of this activity, then we should - * generate a view to display. Otherwise, a dialog will be created in - * onCreateDialog() - */ - if (mEmbedded) { - View view = inflater.inflate(R.layout.wallpaper_chooser, container, false); - view.setBackground(mWallpaperDrawable); - - final Gallery gallery = (Gallery) view.findViewById(R.id.gallery); - gallery.setCallbackDuringFling(false); - gallery.setOnItemSelectedListener(this); - gallery.setAdapter(new ImageAdapter(getActivity())); - - View setButton = view.findViewById(R.id.set); - setButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - selectWallpaper(gallery.getSelectedItemPosition()); - } - }); - return view; - } - return null; - } - - private void selectWallpaper(int position) { - try { - WallpaperManager wpm = (WallpaperManager) getActivity().getSystemService( - Context.WALLPAPER_SERVICE); - wpm.setResource(mImages.get(position)); - Activity activity = getActivity(); - activity.setResult(Activity.RESULT_OK); - activity.finish(); - } catch (IOException e) { - Log.e(TAG, "Failed to set wallpaper: " + e); - } - } - - // Click handler for the Dialog's GridView - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - selectWallpaper(position); - } - - // Selection handler for the embedded Gallery view - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) { - mLoader.cancel(); - } - mLoader = (WallpaperLoader) new WallpaperLoader().execute(position); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - - private void findWallpapers() { - mThumbs = new ArrayList(24); - mImages = new ArrayList(24); - - final Resources resources = getResources(); - // Context.getPackageName() may return the "original" package name, - // com.android.launcher2; Resources needs the real package name, - // com.android.launcher. So we ask Resources for what it thinks the - // package name should be. - final String packageName = resources.getResourcePackageName(R.array.wallpapers); - - addWallpapers(resources, packageName, R.array.wallpapers); - addWallpapers(resources, packageName, R.array.extra_wallpapers); - } - - private void addWallpapers(Resources resources, String packageName, int list) { - final String[] extras = resources.getStringArray(list); - for (String extra : extras) { - int res = resources.getIdentifier(extra, "drawable", packageName); - if (res != 0) { - final int thumbRes = resources.getIdentifier(extra + "_small", - "drawable", packageName); - - if (thumbRes != 0) { - mThumbs.add(thumbRes); - mImages.add(res); - // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")"); - } - } - } - } - - private class ImageAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter { - private LayoutInflater mLayoutInflater; - - ImageAdapter(Activity activity) { - mLayoutInflater = activity.getLayoutInflater(); - } - - public int getCount() { - return mThumbs.size(); - } - - public Object getItem(int position) { - return position; - } - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View view; - - if (convertView == null) { - view = mLayoutInflater.inflate(R.layout.wallpaper_item, parent, false); - } else { - view = convertView; - } - - ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image); - - int thumbRes = mThumbs.get(position); - image.setImageResource(thumbRes); - Drawable thumbDrawable = image.getDrawable(); - if (thumbDrawable != null) { - thumbDrawable.setDither(true); - } else { - Log.e(TAG, "Error decoding thumbnail resId=" + thumbRes + " for wallpaper #" - + position); - } - - return view; - } - } - - class WallpaperLoader extends AsyncTask { - BitmapFactory.Options mOptions; - - WallpaperLoader() { - mOptions = new BitmapFactory.Options(); - mOptions.inDither = false; - mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; - } - - @Override - protected Bitmap doInBackground(Integer... params) { - if (isCancelled()) return null; - try { - return BitmapFactory.decodeResource(getResources(), - mImages.get(params[0]), mOptions); - } catch (OutOfMemoryError e) { - return null; - } - } - - @Override - protected void onPostExecute(Bitmap b) { - if (b == null) return; - - if (!isCancelled() && !mOptions.mCancel) { - // Help the GC - if (mBitmap != null) { - mBitmap.recycle(); - } - - View v = getView(); - if (v != null) { - mBitmap = b; - mWallpaperDrawable.setBitmap(b); - v.postInvalidate(); - } else { - mBitmap = null; - mWallpaperDrawable.setBitmap(null); - } - mLoader = null; - } else { - b.recycle(); - } - } - - void cancel() { - mOptions.requestCancelDecode(); - super.cancel(true); - } - } - - /** - * Custom drawable that centers the bitmap fed to it. - */ - static class WallpaperDrawable extends Drawable { - - Bitmap mBitmap; - int mIntrinsicWidth; - int mIntrinsicHeight; - - /* package */void setBitmap(Bitmap bitmap) { - mBitmap = bitmap; - if (mBitmap == null) - return; - mIntrinsicWidth = mBitmap.getWidth(); - mIntrinsicHeight = mBitmap.getHeight(); - } - - @Override - public void draw(Canvas canvas) { - if (mBitmap == null) return; - int width = canvas.getWidth(); - int height = canvas.getHeight(); - int x = (width - mIntrinsicWidth) / 2; - int y = (height - mIntrinsicHeight) / 2; - canvas.drawBitmap(mBitmap, x, y, null); - } - - @Override - public int getOpacity() { - return android.graphics.PixelFormat.OPAQUE; - } - - @Override - public void setAlpha(int alpha) { - // Ignore - } - - @Override - public void setColorFilter(ColorFilter cf) { - // Ignore - } - } -} diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java deleted file mode 100644 index 2d2340a3e..000000000 --- a/src/com/android/launcher2/Workspace.java +++ /dev/null @@ -1,3781 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2; - -import android.animation.Animator; -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.appwidget.AppWidgetHostView; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Camera; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.Region.Op; -import android.graphics.drawable.Drawable; -import android.os.IBinder; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.DecelerateInterpolator; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.launcher.R; -import com.android.launcher2.FolderIcon.FolderRingAnimator; -import com.android.launcher2.LauncherSettings.Favorites; - -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * 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 SmoothPagedView - implements DropTarget, DragSource, DragScroller, View.OnTouchListener, - DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener { - private static final String TAG = "Launcher.Workspace"; - - // Y rotation to apply to the workspace screens - private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f; - - private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0; - private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; - private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; - - private static final int BACKGROUND_FADE_OUT_DURATION = 350; - private static final int ADJACENT_SCREEN_DROP_DURATION = 300; - private static final int FLING_THRESHOLD_VELOCITY = 500; - - // These animators are used to fade the children's outlines - private ObjectAnimator mChildrenOutlineFadeInAnimation; - private ObjectAnimator mChildrenOutlineFadeOutAnimation; - private float mChildrenOutlineAlpha = 0; - - // These properties refer to the background protection gradient used for AllApps and Customize - private ValueAnimator mBackgroundFadeInAnimation; - private ValueAnimator mBackgroundFadeOutAnimation; - private Drawable mBackground; - boolean mDrawBackground = true; - private float mBackgroundAlpha = 0; - private float mOverScrollMaxBackgroundAlpha = 0.0f; - - private float mWallpaperScrollRatio = 1.0f; - - private final WallpaperManager mWallpaperManager; - private IBinder mWindowToken; - private static final float WALLPAPER_SCREENS_SPAN = 2f; - - private int mDefaultPage; - - /** - * CellInfo for the cell that is currently being dragged - */ - private CellLayout.CellInfo mDragInfo; - - /** - * Target drop area calculated during last acceptDrop call. - */ - private int[] mTargetCell = new int[2]; - private int mDragOverX = -1; - private int mDragOverY = -1; - - static Rect mLandscapeCellLayoutMetrics = null; - static Rect mPortraitCellLayoutMetrics = null; - - /** - * The CellLayout that is currently being dragged over - */ - private CellLayout mDragTargetLayout = null; - /** - * The CellLayout that we will show as glowing - */ - private CellLayout mDragOverlappingLayout = null; - - /** - * The CellLayout which will be dropped to - */ - private CellLayout mDropToLayout = null; - - private Launcher mLauncher; - private IconCache mIconCache; - private DragController mDragController; - - // 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[] mDragViewVisualCenter = new float[2]; - private float[] mTempDragCoordinates = new float[2]; - private float[] mTempCellLayoutCenterCoordinates = new float[2]; - private float[] mTempDragBottomRightCoordinates = new float[2]; - private Matrix mTempInverseMatrix = new Matrix(); - - private SpringLoadedDragController mSpringLoadedDragController; - private float mSpringLoadedShrinkFactor; - - private static final int DEFAULT_CELL_COUNT_X = 4; - private static final int DEFAULT_CELL_COUNT_Y = 4; - - // State variable that indicates whether the pages are small (ie when you're - // in all apps or customize mode) - - enum State { NORMAL, SPRING_LOADED, SMALL }; - private State mState = State.NORMAL; - private boolean mIsSwitchingState = false; - - boolean mAnimatingViewIntoPlace = false; - boolean mIsDragOccuring = false; - boolean mChildrenLayersEnabled = true; - - /** Is the user is dragging an item near the edge of a page? */ - private boolean mInScrollArea = 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 float mOverscrollFade = 0; - private boolean mOverscrollTransformsSet; - public static final int DRAG_BITMAP_PADDING = 2; - private boolean mWorkspaceFadeInAdjacentScreens; - - // Camera and Matrix used to determine the final position of a neighboring CellLayout - private final Matrix mMatrix = new Matrix(); - private final Camera mCamera = new Camera(); - private final float mTempFloat2[] = new float[2]; - - enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM }; - int mWallpaperWidth; - int mWallpaperHeight; - WallpaperOffsetInterpolator mWallpaperOffset; - boolean mUpdateWallpaperOffsetImmediately = false; - private Runnable mDelayedResizeRunnable; - private Runnable mDelayedSnapToPageRunnable; - private Point mDisplaySize = new Point(); - private boolean mIsStaticWallpaper; - private int mWallpaperTravelWidth; - private int mSpringLoadedPageSpacing; - private int mCameraDistance; - - // Variables relating to the creation of user folders by hovering shortcuts over shortcuts - private static final int FOLDER_CREATION_TIMEOUT = 0; - private static final int REORDER_TIMEOUT = 250; - private final Alarm mFolderCreationAlarm = new Alarm(); - private final Alarm mReorderAlarm = new Alarm(); - private FolderRingAnimator mDragFolderRingAnimator = null; - private FolderIcon mDragOverFolderIcon = null; - private boolean mCreateUserFolderOnDrop = false; - private boolean mAddToExistingFolderOnDrop = false; - private DropTarget.DragEnforcer mDragEnforcer; - private float mMaxDistanceForFolderCreation; - - // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) - private float mXDown; - private float mYDown; - final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; - final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; - final static float TOUCH_SLOP_DAMPING_FACTOR = 4; - - // Relating to the animation of items being dropped externally - public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0; - public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1; - public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2; - public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3; - public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4; - - // Related to dragging, folder creation and reordering - private static final int DRAG_MODE_NONE = 0; - private static final int DRAG_MODE_CREATE_FOLDER = 1; - private static final int DRAG_MODE_ADD_TO_FOLDER = 2; - private static final int DRAG_MODE_REORDER = 3; - private int mDragMode = DRAG_MODE_NONE; - private int mLastReorderX = -1; - private int mLastReorderY = -1; - - // These variables are used for storing the initial and final values during workspace animations - private int mSavedScrollX; - private float mSavedRotationY; - private float mSavedTranslationX; - private float mCurrentScaleX; - private float mCurrentScaleY; - private float mCurrentRotationY; - private float mCurrentTranslationX; - private float mCurrentTranslationY; - private float[] mOldTranslationXs; - private float[] mOldTranslationYs; - private float[] mOldScaleXs; - private float[] mOldScaleYs; - private float[] mOldBackgroundAlphas; - private float[] mOldAlphas; - private float[] mNewTranslationXs; - private float[] mNewTranslationYs; - private float[] mNewScaleXs; - private float[] mNewScaleYs; - private float[] mNewBackgroundAlphas; - private float[] mNewAlphas; - private float[] mNewRotationYs; - private float mTransitionProgress; - - /** - * 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 Workspace(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 Workspace(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mContentIsRefreshable = false; - - mDragEnforcer = new DropTarget.DragEnforcer(context); - // With workspace, data is available straight from the get-go - setDataIsReady(); - - final Resources res = getResources(); - mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens); - mFadeInAdjacentScreens = false; - mWallpaperManager = WallpaperManager.getInstance(context); - - int cellCountX = DEFAULT_CELL_COUNT_X; - int cellCountY = DEFAULT_CELL_COUNT_Y; - - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.Workspace, defStyle, 0); - - if (LauncherApplication.isScreenLarge()) { - // Determine number of rows/columns dynamically - // TODO: This code currently fails on tablets with an aspect ratio < 1.3. - // Around that ratio we should make cells the same size in portrait and - // landscape - TypedArray actionBarSizeTypedArray = - context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize }); - DisplayMetrics displayMetrics = res.getDisplayMetrics(); - final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f); - final float systemBarHeight = res.getDimension(R.dimen.status_bar_height); - final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp * - displayMetrics.density; - - cellCountX = 1; - while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) { - cellCountX++; - } - - cellCountY = 1; - while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1) - <= smallestScreenDim - systemBarHeight) { - cellCountY++; - } - } - - mSpringLoadedShrinkFactor = - res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; - mSpringLoadedPageSpacing = - res.getDimensionPixelSize(R.dimen.workspace_spring_loaded_page_spacing); - mCameraDistance = res.getInteger(R.integer.config_cameraDistance); - - // if the value is manually specified, use that instead - cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX); - cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY); - mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); - a.recycle(); - - setOnHierarchyChangeListener(this); - - LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); - setHapticFeedbackEnabled(false); - - mLauncher = (Launcher) context; - initWorkspace(); - - // Disable multitouch across the workspace/all apps/customize tray - setMotionEventSplittingEnabled(true); - - // Unless otherwise specified this view is important for accessibility. - if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - } - - // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each - // dimension if unsuccessful - public int[] estimateItemSize(int hSpan, int vSpan, - ItemInfo itemInfo, boolean springLoaded) { - int[] size = new int[2]; - if (getChildCount() > 0) { - CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0); - Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan); - size[0] = r.width(); - size[1] = r.height(); - if (springLoaded) { - size[0] *= mSpringLoadedShrinkFactor; - size[1] *= mSpringLoadedShrinkFactor; - } - return size; - } else { - size[0] = Integer.MAX_VALUE; - size[1] = Integer.MAX_VALUE; - return size; - } - } - public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo, - int hCell, int vCell, int hSpan, int vSpan) { - Rect r = new Rect(); - cl.cellToRect(hCell, vCell, hSpan, vSpan, r); - return r; - } - - public void buildPageHardwareLayers() { - if (getWindowToken() != null) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - CellLayout cl = (CellLayout) getChildAt(i); - cl.getShortcutsAndWidgets().buildLayer(); - } - } - } - - public void onDragStart(DragSource source, Object info, int dragAction) { - mIsDragOccuring = true; - updateChildrenLayersEnabled(); - mLauncher.lockScreenOrientation(); - setChildrenBackgroundAlphaMultipliers(1f); - // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging - InstallShortcutReceiver.enableInstallQueue(); - UninstallShortcutReceiver.enableUninstallQueue(); - } - - public void onDragEnd() { - mIsDragOccuring = false; - updateChildrenLayersEnabled(); - mLauncher.unlockScreenOrientation(false); - - // Re-enable any Un/InstallShortcutReceiver and now process any queued items - InstallShortcutReceiver.disableAndFlushInstallQueue(getContext()); - UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext()); - } - - /** - * Initializes various states for this workspace. - */ - protected void initWorkspace() { - Context context = getContext(); - mCurrentPage = mDefaultPage; - Launcher.setScreen(mCurrentPage); - LauncherApplication app = (LauncherApplication)context.getApplicationContext(); - mIconCache = app.getIconCache(); - setWillNotDraw(false); - setChildrenDrawnWithCacheEnabled(true); - - final Resources res = getResources(); - try { - mBackground = res.getDrawable(R.drawable.apps_customize_bg); - } catch (Resources.NotFoundException e) { - // In this case, we will skip drawing background protection - } - - mWallpaperOffset = new WallpaperOffsetInterpolator(); - Display display = mLauncher.getWindowManager().getDefaultDisplay(); - display.getSize(mDisplaySize); - mWallpaperTravelWidth = (int) (mDisplaySize.x * - wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y)); - - mMaxDistanceForFolderCreation = (0.55f * res.getDimensionPixelSize(R.dimen.app_icon_size)); - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); - } - - @Override - protected int getScrollMode() { - return SmoothPagedView.X_LARGE_MODE; - } - - @Override - public void onChildViewAdded(View parent, View child) { - if (!(child instanceof CellLayout)) { - throw new IllegalArgumentException("A Workspace can only have CellLayout children."); - } - CellLayout cl = ((CellLayout) child); - cl.setOnInterceptTouchListener(this); - cl.setClickable(true); - cl.enableHardwareLayers(); - cl.setContentDescription(getContext().getString( - R.string.workspace_description_format, getChildCount())); - } - - @Override - public void onChildViewRemoved(View parent, View child) { - } - - protected boolean shouldDrawChild(View child) { - final CellLayout cl = (CellLayout) child; - return super.shouldDrawChild(child) && - (cl.getShortcutsAndWidgets().getAlpha() > 0 || - cl.getBackgroundAlpha() > 0); - } - - /** - * @return The open folder on the current screen, or null if there is none - */ - Folder getOpenFolder() { - DragLayer dragLayer = mLauncher.getDragLayer(); - int count = dragLayer.getChildCount(); - for (int i = 0; i < count; i++) { - View child = dragLayer.getChildAt(i); - if (child instanceof Folder) { - Folder folder = (Folder) child; - if (folder.getInfo().opened) - return folder; - } - } - return null; - } - - boolean isTouchActive() { - return mTouchState != TOUCH_STATE_REST; - } - - /** - * Adds the specified child in the specified 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 screen The screen in which to add the child. - * @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 addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) { - addInScreen(child, container, screen, x, y, spanX, spanY, false); - } - - /** - * Adds the specified child in the specified 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 screen The screen in which to add the child. - * @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. - */ - void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY, - boolean insert) { - if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (screen < 0 || screen >= getChildCount()) { - Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() - + " (was " + screen + "); skipping child"); - return; - } - } - - final CellLayout layout; - if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - layout = mLauncher.getHotseat().getLayout(); - child.setOnKeyListener(null); - - // Hide folder title in the hotseat - if (child instanceof FolderIcon) { - ((FolderIcon) child).setTextVisible(false); - } - - if (screen < 0) { - screen = mLauncher.getHotseat().getOrderInHotseat(x, y); - } else { - // Note: We do this to ensure that the hotseat is always laid out in the orientation - // of the hotseat in order regardless of which orientation they were added - x = mLauncher.getHotseat().getCellXFromOrder(screen); - y = mLauncher.getHotseat().getCellYFromOrder(screen); - } - } else { - // Show folder title if not in the hotseat - if (child instanceof FolderIcon) { - ((FolderIcon) child).setTextVisible(true); - } - - layout = (CellLayout) getChildAt(screen); - child.setOnKeyListener(new IconKeyEventListener()); - } - - LayoutParams genericLp = child.getLayoutParams(); - CellLayout.LayoutParams lp; - if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) { - lp = new CellLayout.LayoutParams(x, y, spanX, spanY); - } else { - lp = (CellLayout.LayoutParams) genericLp; - lp.cellX = x; - lp.cellY = y; - lp.cellHSpan = spanX; - lp.cellVSpan = spanY; - } - - if (spanX < 0 && spanY < 0) { - lp.isLockedToGrid = false; - } - - // Get the canonical child id to uniquely represent this view in this screen - int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY); - boolean markCellsAsOccupied = !(child instanceof Folder); - if (!layout.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); - } - } - - /** - * Check if the point (x, y) hits a given page. - */ - private boolean hitsPage(int index, float x, float y) { - final View page = getChildAt(index); - if (page != null) { - float[] localXY = { x, y }; - mapPointFromSelfToChild(page, localXY); - return (localXY[0] >= 0 && localXY[0] < page.getWidth() - && localXY[1] >= 0 && localXY[1] < page.getHeight()); - } - return false; - } - - @Override - protected boolean hitsPreviousPage(float x, float y) { - // mNextPage is set to INVALID_PAGE whenever we are stationary. - // Calculating "next page" this way ensures that you scroll to whatever page you tap on - final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; - - // Only allow tap to next page on large devices, where there's significant margin outside - // the active workspace - return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y); - } - - @Override - protected boolean hitsNextPage(float x, float y) { - // mNextPage is set to INVALID_PAGE whenever we are stationary. - // Calculating "next page" this way ensures that you scroll to whatever page you tap on - final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; - - // Only allow tap to next page on large devices, where there's significant margin outside - // the active workspace - return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y); - } - - /** - * Called directly from a CellLayout (not by the framework), after we've been added as a - * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout - * that it should intercept touch events, which is not something that is normally supported. - */ - @Override - public boolean onTouch(View v, MotionEvent event) { - return (isSmall() || !isFinishedSwitchingState()); - } - - public boolean isSwitchingState() { - return mIsSwitchingState; - } - - /** This differs from isSwitchingState in that we take into account how far the transition - * has completed. */ - public boolean isFinishedSwitchingState() { - return !mIsSwitchingState || (mTransitionProgress > 0.5f); - } - - protected void onWindowVisibilityChanged (int visibility) { - mLauncher.onWindowVisibilityChanged(visibility); - } - - @Override - public boolean dispatchUnhandledMove(View focused, int direction) { - if (isSmall() || !isFinishedSwitchingState()) { - // when the home screens are shrunken, shouldn't allow side-scrolling - return false; - } - return super.dispatchUnhandledMove(focused, direction); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - switch (ev.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - mXDown = ev.getX(); - mYDown = ev.getY(); - break; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_UP: - if (mTouchState == TOUCH_STATE_REST) { - final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); - if (!currentPage.lastDownOnOccupiedCell()) { - onWallpaperTap(ev); - } - } - } - return super.onInterceptTouchEvent(ev); - } - - protected void reinflateWidgetsIfNecessary() { - final int clCount = getChildCount(); - for (int i = 0; i < clCount; i++) { - CellLayout cl = (CellLayout) getChildAt(i); - ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets(); - final int itemCount = swc.getChildCount(); - for (int j = 0; j < itemCount; j++) { - View v = swc.getChildAt(j); - - if (v.getTag() instanceof LauncherAppWidgetInfo) { - LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); - LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView; - if (lahv != null && lahv.orientationChangedSincedInflation()) { - mLauncher.removeAppWidget(info); - // Remove the current widget which is inflated with the wrong orientation - cl.removeView(lahv); - mLauncher.bindAppWidget(info); - } - } - } - } - } - - @Override - protected void determineScrollingStart(MotionEvent ev) { - if (isSmall()) return; - if (!isFinishedSwitchingState()) return; - - float deltaX = Math.abs(ev.getX() - mXDown); - float deltaY = Math.abs(ev.getY() - mYDown); - - if (Float.compare(deltaX, 0f) == 0) return; - - float slope = deltaY / deltaX; - float theta = (float) Math.atan(slope); - - if (deltaX > mTouchSlop || deltaY > mTouchSlop) { - cancelCurrentPageLongPress(); - } - - if (theta > MAX_SWIPE_ANGLE) { - // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace - return; - } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { - // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to - // increase the touch slop to make it harder to begin scrolling the workspace. This - // results in vertically scrolling widgets to more easily. The higher the angle, the - // more we increase touch slop. - theta -= START_DAMPING_TOUCH_SLOP_ANGLE; - float extraRatio = (float) - Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); - super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); - } else { - // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special - super.determineScrollingStart(ev); - } - } - - @Override - protected boolean isScrollingIndicatorEnabled() { - return super.isScrollingIndicatorEnabled() && (mState != State.SPRING_LOADED); - } - - protected void onPageBeginMoving() { - super.onPageBeginMoving(); - - if (isHardwareAccelerated()) { - updateChildrenLayersEnabled(); - } else { - 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); - } - } - - // Only show page outlines as we pan if we are on large screen - if (LauncherApplication.isScreenLarge()) { - showOutlines(); - mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null; - } - - // If we are not fading in adjacent screens, we still need to restore the alpha in case the - // user scrolls while we are transitioning (should not affect dispatchDraw optimizations) - if (!mWorkspaceFadeInAdjacentScreens) { - for (int i = 0; i < getChildCount(); ++i) { - ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f); - } - } - - // Show the scroll indicator as you pan the page - showScrollingIndicator(false); - } - - protected void onPageEndMoving() { - super.onPageEndMoving(); - - if (isHardwareAccelerated()) { - updateChildrenLayersEnabled(); - } else { - clearChildrenCache(); - } - - - if (mDragController.isDragging()) { - if (isSmall()) { - // If we are in springloaded mode, then force an event to check if the current touch - // is under a new page (to scroll to) - mDragController.forceMoveEvent(); - } - } else { - // If we are not mid-dragging, hide the page outlines if we are on a large screen - if (LauncherApplication.isScreenLarge()) { - hideOutlines(); - } - - // Hide the scroll indicator as you pan the page - if (!mDragController.isDragging()) { - hideScrollingIndicator(false); - } - } - mOverScrollMaxBackgroundAlpha = 0.0f; - - if (mDelayedResizeRunnable != null) { - mDelayedResizeRunnable.run(); - mDelayedResizeRunnable = null; - } - - if (mDelayedSnapToPageRunnable != null) { - mDelayedSnapToPageRunnable.run(); - mDelayedSnapToPageRunnable = null; - } - } - - @Override - protected void notifyPageSwitchListener() { - super.notifyPageSwitchListener(); - Launcher.setScreen(mCurrentPage); - }; - - // As a ratio of screen height, the total distance we want the parallax effect to span - // horizontally - private float wallpaperTravelToScreenWidthRatio(int width, int height) { - float aspectRatio = width / (float) height; - - // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width - // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width - // We will use these two data points to extrapolate how much the wallpaper parallax effect - // to span (ie travel) at any aspect ratio: - - final float ASPECT_RATIO_LANDSCAPE = 16/10f; - final float ASPECT_RATIO_PORTRAIT = 10/16f; - final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; - final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; - - // To find out the desired width at different aspect ratios, we use the following two - // formulas, where the coefficient on x is the aspect ratio (width/height): - // (16/10)x + y = 1.5 - // (10/16)x + y = 1.2 - // We solve for x and y and end up with a final formula: - final float x = - (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / - (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); - final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; - return x * aspectRatio + y; - } - - // The range of scroll values for Workspace - private int getScrollRange() { - return getChildOffset(getChildCount() - 1) - getChildOffset(0); - } - - protected void setWallpaperDimension() { - DisplayMetrics displayMetrics = new DisplayMetrics(); - mLauncher.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics); - final int maxDim = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); - final int minDim = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); - - // We need to ensure that there is enough extra space in the wallpaper for the intended - // parallax effects - if (LauncherApplication.isScreenLarge()) { - mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); - mWallpaperHeight = maxDim; - } else { - mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); - mWallpaperHeight = maxDim; - } - new Thread("setWallpaperDimension") { - public void run() { - mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight); - } - }.start(); - } - - private float wallpaperOffsetForCurrentScroll() { - // Set wallpaper offset steps (1 / (number of screens - 1)) - mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f); - - // For the purposes of computing the scrollRange and overScrollOffset, we assume - // that mLayoutScale is 1. This means that when we're in spring-loaded mode, - // there's no discrepancy between the wallpaper offset for a given page. - float layoutScale = mLayoutScale; - mLayoutScale = 1f; - int scrollRange = getScrollRange(); - - // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale - float adjustedScrollX = Math.max(0, Math.min(getScrollX(), mMaxScrollX)); - adjustedScrollX *= mWallpaperScrollRatio; - mLayoutScale = layoutScale; - - float scrollProgress = - adjustedScrollX / (float) scrollRange; - - if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) { - // The wallpaper travel width is how far, from left to right, the wallpaper will move - // at this orientation. On tablets in portrait mode we don't move all the way to the - // edges of the wallpaper, or otherwise the parallax effect would be too strong. - int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth); - - float offsetInDips = wallpaperTravelWidth * scrollProgress + - (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it - float offset = offsetInDips / (float) mWallpaperWidth; - return offset; - } else { - return scrollProgress; - } - } - - private void syncWallpaperOffsetWithScroll() { - final boolean enableWallpaperEffects = isHardwareAccelerated(); - if (enableWallpaperEffects) { - mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll()); - } - } - - public void updateWallpaperOffsetImmediately() { - mUpdateWallpaperOffsetImmediately = true; - } - - private void updateWallpaperOffsets() { - boolean updateNow = false; - boolean keepUpdating = true; - if (mUpdateWallpaperOffsetImmediately) { - updateNow = true; - keepUpdating = false; - mWallpaperOffset.jumpToFinal(); - mUpdateWallpaperOffsetImmediately = false; - } else { - updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset(); - } - if (updateNow) { - if (mWindowToken != null) { - mWallpaperManager.setWallpaperOffsets(mWindowToken, - mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY()); - } - } - if (keepUpdating) { - invalidate(); - } - } - - @Override - protected void updateCurrentPageScroll() { - super.updateCurrentPageScroll(); - computeWallpaperScrollRatio(mCurrentPage); - } - - @Override - protected void snapToPage(int whichPage) { - super.snapToPage(whichPage); - computeWallpaperScrollRatio(whichPage); - } - - @Override - protected void snapToPage(int whichPage, int duration) { - super.snapToPage(whichPage, duration); - computeWallpaperScrollRatio(whichPage); - } - - protected void snapToPage(int whichPage, Runnable r) { - if (mDelayedSnapToPageRunnable != null) { - mDelayedSnapToPageRunnable.run(); - } - mDelayedSnapToPageRunnable = r; - snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION); - } - - private void computeWallpaperScrollRatio(int page) { - // Here, we determine what the desired scroll would be with and without a layout scale, - // and compute a ratio between the two. This allows us to adjust the wallpaper offset - // as though there is no layout scale. - float layoutScale = mLayoutScale; - int scaled = getChildOffset(page) - getRelativeChildOffset(page); - mLayoutScale = 1.0f; - float unscaled = getChildOffset(page) - getRelativeChildOffset(page); - mLayoutScale = layoutScale; - if (scaled > 0) { - mWallpaperScrollRatio = (1.0f * unscaled) / scaled; - } else { - mWallpaperScrollRatio = 1f; - } - } - - class WallpaperOffsetInterpolator { - float mFinalHorizontalWallpaperOffset = 0.0f; - float mFinalVerticalWallpaperOffset = 0.5f; - float mHorizontalWallpaperOffset = 0.0f; - float mVerticalWallpaperOffset = 0.5f; - long mLastWallpaperOffsetUpdateTime; - boolean mIsMovingFast; - boolean mOverrideHorizontalCatchupConstant; - float mHorizontalCatchupConstant = 0.35f; - float mVerticalCatchupConstant = 0.35f; - - public WallpaperOffsetInterpolator() { - } - - public void setOverrideHorizontalCatchupConstant(boolean override) { - mOverrideHorizontalCatchupConstant = override; - } - - public void setHorizontalCatchupConstant(float f) { - mHorizontalCatchupConstant = f; - } - - public void setVerticalCatchupConstant(float f) { - mVerticalCatchupConstant = f; - } - - public boolean computeScrollOffset() { - if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 && - Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) { - mIsMovingFast = false; - return false; - } - boolean isLandscape = mDisplaySize.x > mDisplaySize.y; - - long currentTime = System.currentTimeMillis(); - long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime; - timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate); - timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate); - - float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset); - if (!mIsMovingFast && xdiff > 0.07) { - mIsMovingFast = true; - } - - float fractionToCatchUpIn1MsHorizontal; - if (mOverrideHorizontalCatchupConstant) { - fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant; - } else if (mIsMovingFast) { - fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f; - } else { - // slow - fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f; - } - float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant; - - fractionToCatchUpIn1MsHorizontal /= 33f; - fractionToCatchUpIn1MsVertical /= 33f; - - final float UPDATE_THRESHOLD = 0.00001f; - float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset; - float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset; - boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD && - Math.abs(vOffsetDelta) < UPDATE_THRESHOLD; - - // Don't have any lag between workspace and wallpaper on non-large devices - if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) { - mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; - mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; - } else { - float percentToCatchUpVertical = - Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical); - float percentToCatchUpHorizontal = - Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal); - mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta; - mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta; - } - - mLastWallpaperOffsetUpdateTime = System.currentTimeMillis(); - return true; - } - - public float getCurrX() { - return mHorizontalWallpaperOffset; - } - - public float getFinalX() { - return mFinalHorizontalWallpaperOffset; - } - - public float getCurrY() { - return mVerticalWallpaperOffset; - } - - public float getFinalY() { - return mFinalVerticalWallpaperOffset; - } - - public void setFinalX(float x) { - mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f)); - } - - public void setFinalY(float y) { - mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f)); - } - - public void jumpToFinal() { - mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; - mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; - } - } - - @Override - public void computeScroll() { - super.computeScroll(); - syncWallpaperOffsetWithScroll(); - } - - void showOutlines() { - if (!isSmall() && !mIsSwitchingState) { - if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); - if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); - mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f); - mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION); - mChildrenOutlineFadeInAnimation.start(); - } - } - - void hideOutlines() { - if (!isSmall() && !mIsSwitchingState) { - if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); - if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); - mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f); - mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION); - mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY); - mChildrenOutlineFadeOutAnimation.start(); - } - } - - public void showOutlinesTemporarily() { - if (!mIsPageMoving && !isTouchActive()) { - snapToPage(mCurrentPage); - } - } - - public void setChildrenOutlineAlpha(float alpha) { - mChildrenOutlineAlpha = alpha; - for (int i = 0; i < getChildCount(); i++) { - CellLayout cl = (CellLayout) getChildAt(i); - cl.setBackgroundAlpha(alpha); - } - } - - public float getChildrenOutlineAlpha() { - return mChildrenOutlineAlpha; - } - - void disableBackground() { - mDrawBackground = false; - } - void enableBackground() { - mDrawBackground = true; - } - - private void animateBackgroundGradient(float finalAlpha, boolean animated) { - if (mBackground == null) return; - if (mBackgroundFadeInAnimation != null) { - mBackgroundFadeInAnimation.cancel(); - mBackgroundFadeInAnimation = null; - } - if (mBackgroundFadeOutAnimation != null) { - mBackgroundFadeOutAnimation.cancel(); - mBackgroundFadeOutAnimation = null; - } - float startAlpha = getBackgroundAlpha(); - if (finalAlpha != startAlpha) { - if (animated) { - mBackgroundFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha); - mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue()); - } - }); - mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f)); - mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION); - mBackgroundFadeOutAnimation.start(); - } else { - setBackgroundAlpha(finalAlpha); - } - } - } - - public void setBackgroundAlpha(float alpha) { - if (alpha != mBackgroundAlpha) { - mBackgroundAlpha = alpha; - invalidate(); - } - } - - public float getBackgroundAlpha() { - return mBackgroundAlpha; - } - - /** - * Due to 3D transformations, if two CellLayouts are theoretically touching each other, - * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived - * as being larger. This method computes what offset the rotated view should be translated - * in order to minimize this perceived gap. - * @param degrees Angle of the view - * @param width Width of the view - * @param height Height of the view - * @return Offset to be used in a View.setTranslationX() call - */ - private float getOffsetXForRotation(float degrees, int width, int height) { - mMatrix.reset(); - mCamera.save(); - mCamera.rotateY(Math.abs(degrees)); - mCamera.getMatrix(mMatrix); - mCamera.restore(); - - mMatrix.preTranslate(-width * 0.5f, -height * 0.5f); - mMatrix.postTranslate(width * 0.5f, height * 0.5f); - mTempFloat2[0] = width; - mTempFloat2[1] = height; - mMatrix.mapPoints(mTempFloat2); - return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f); - } - - float backgroundAlphaInterpolator(float r) { - float pivotA = 0.1f; - float pivotB = 0.4f; - if (r < pivotA) { - return 0; - } else if (r > pivotB) { - return 1.0f; - } else { - return (r - pivotA)/(pivotB - pivotA); - } - } - - float overScrollBackgroundAlphaInterpolator(float r) { - float threshold = 0.08f; - - if (r > mOverScrollMaxBackgroundAlpha) { - mOverScrollMaxBackgroundAlpha = r; - } else if (r < mOverScrollMaxBackgroundAlpha) { - r = mOverScrollMaxBackgroundAlpha; - } - - return Math.min(r / threshold, 1.0f); - } - - private void updatePageAlphaValues(int screenCenter) { - boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; - if (mWorkspaceFadeInAdjacentScreens && - mState == State.NORMAL && - !mIsSwitchingState && - !isInOverscroll) { - for (int i = 0; i < getChildCount(); i++) { - CellLayout child = (CellLayout) getChildAt(i); - if (child != null) { - float scrollProgress = getScrollProgress(screenCenter, child, i); - float alpha = 1 - Math.abs(scrollProgress); - child.getShortcutsAndWidgets().setAlpha(alpha); - if (!mIsDragOccuring) { - child.setBackgroundAlphaMultiplier( - backgroundAlphaInterpolator(Math.abs(scrollProgress))); - } else { - child.setBackgroundAlphaMultiplier(1f); - } - } - } - } - } - - private void setChildrenBackgroundAlphaMultipliers(float a) { - for (int i = 0; i < getChildCount(); i++) { - CellLayout child = (CellLayout) getChildAt(i); - child.setBackgroundAlphaMultiplier(a); - } - } - - @Override - protected void screenScrolled(int screenCenter) { - super.screenScrolled(screenCenter); - - updatePageAlphaValues(screenCenter); - - if (mOverScrollX < 0 || mOverScrollX > mMaxScrollX) { - int index = mOverScrollX < 0 ? 0 : getChildCount() - 1; - CellLayout cl = (CellLayout) getChildAt(index); - float scrollProgress = getScrollProgress(screenCenter, cl, index); - cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0); - float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress; - cl.setRotationY(rotation); - setFadeForOverScroll(Math.abs(scrollProgress)); - if (!mOverscrollTransformsSet) { - mOverscrollTransformsSet = true; - cl.setCameraDistance(mDensity * mCameraDistance); - cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f)); - cl.setPivotY(cl.getMeasuredHeight() * 0.5f); - cl.setOverscrollTransformsDirty(true); - } - } else { - if (mOverscrollFade != 0) { - setFadeForOverScroll(0); - } - if (mOverscrollTransformsSet) { - mOverscrollTransformsSet = false; - ((CellLayout) getChildAt(0)).resetOverscrollTransforms(); - ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms(); - } - } - } - - @Override - protected void overScroll(float amount) { - acceleratedOverScroll(amount); - } - - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mWindowToken = getWindowToken(); - computeScroll(); - mDragController.setWindowToken(mWindowToken); - } - - protected void onDetachedFromWindow() { - mWindowToken = null; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { - mUpdateWallpaperOffsetImmediately = true; - } - super.onLayout(changed, left, top, right, bottom); - } - - @Override - protected void onDraw(Canvas canvas) { - updateWallpaperOffsets(); - - // Draw the background gradient if necessary - if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) { - int alpha = (int) (mBackgroundAlpha * 255); - mBackground.setAlpha(alpha); - mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(), - getMeasuredHeight()); - mBackground.draw(canvas); - } - - super.onDraw(canvas); - } - - boolean isDrawingBackgroundGradient() { - return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground); - } - - @Override - protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - if (!mLauncher.isAllAppsVisible()) { - final Folder openFolder = getOpenFolder(); - if (openFolder != null) { - return openFolder.requestFocus(direction, previouslyFocusedRect); - } else { - return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); - } - } - return false; - } - - @Override - public int getDescendantFocusability() { - if (isSmall()) { - return ViewGroup.FOCUS_BLOCK_DESCENDANTS; - } - return super.getDescendantFocusability(); - } - - @Override - public void addFocusables(ArrayList views, int direction, int focusableMode) { - if (!mLauncher.isAllAppsVisible()) { - final Folder openFolder = getOpenFolder(); - if (openFolder != null) { - openFolder.addFocusables(views, direction); - } else { - super.addFocusables(views, direction, focusableMode); - } - } - } - - public boolean isSmall() { - return mState == State.SMALL || mState == State.SPRING_LOADED; - } - - void enableChildrenCache(int fromPage, int toPage) { - if (fromPage > toPage) { - final int temp = fromPage; - fromPage = toPage; - toPage = temp; - } - - final int screenCount = getChildCount(); - - fromPage = Math.max(fromPage, 0); - toPage = Math.min(toPage, screenCount - 1); - - for (int i = fromPage; i <= toPage; i++) { - final CellLayout layout = (CellLayout) getChildAt(i); - layout.setChildrenDrawnWithCacheEnabled(true); - layout.setChildrenDrawingCacheEnabled(true); - } - } - - void clearChildrenCache() { - final int screenCount = getChildCount(); - for (int i = 0; i < screenCount; i++) { - final CellLayout layout = (CellLayout) getChildAt(i); - layout.setChildrenDrawnWithCacheEnabled(false); - // In software mode, we don't want the items to continue to be drawn into bitmaps - if (!isHardwareAccelerated()) { - layout.setChildrenDrawingCacheEnabled(false); - } - } - } - - private void updateChildrenLayersEnabled() { - boolean small = mState == State.SMALL || mIsSwitchingState; - boolean enableChildrenLayers = small || mAnimatingViewIntoPlace || isPageMoving(); - - if (enableChildrenLayers != mChildrenLayersEnabled) { - mChildrenLayersEnabled = enableChildrenLayers; - for (int i = 0; i < getPageCount(); i++) { - ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(mChildrenLayersEnabled); - } - } - } - - protected void onWallpaperTap(MotionEvent ev) { - final int[] position = mTempCell; - getLocationOnScreen(position); - - int pointerIndex = ev.getActionIndex(); - position[0] += (int) ev.getX(pointerIndex); - position[1] += (int) ev.getY(pointerIndex); - - mWallpaperManager.sendWallpaperCommand(getWindowToken(), - ev.getAction() == MotionEvent.ACTION_UP - ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, - position[0], position[1], 0, null); - } - - /* - * This interpolator emulates the rate at which the perceived scale of an object changes - * as its distance from a camera increases. When this interpolator is applied to a scale - * animation on a view, it evokes the sense that the object is shrinking due to moving away - * from the camera. - */ - static class ZInterpolator implements TimeInterpolator { - private float focalLength; - - public ZInterpolator(float foc) { - focalLength = foc; - } - - public float getInterpolation(float input) { - return (1.0f - focalLength / (focalLength + input)) / - (1.0f - focalLength / (focalLength + 1.0f)); - } - } - - /* - * The exact reverse of ZInterpolator. - */ - static class InverseZInterpolator implements TimeInterpolator { - private ZInterpolator zInterpolator; - public InverseZInterpolator(float foc) { - zInterpolator = new ZInterpolator(foc); - } - public float getInterpolation(float input) { - return 1 - zInterpolator.getInterpolation(1 - input); - } - } - - /* - * ZInterpolator compounded with an ease-out. - */ - static class ZoomOutInterpolator implements TimeInterpolator { - private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f); - private final ZInterpolator zInterpolator = new ZInterpolator(0.13f); - - public float getInterpolation(float input) { - return decelerate.getInterpolation(zInterpolator.getInterpolation(input)); - } - } - - /* - * InvereZInterpolator compounded with an ease-out. - */ - static class ZoomInInterpolator implements TimeInterpolator { - private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f); - private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f); - - public float getInterpolation(float input) { - return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input)); - } - } - - private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator(); - - /* - * - * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) 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). - * - */ - public void onDragStartedWithItem(View v) { - final Canvas canvas = new Canvas(); - - // The outline is used to visualize where the item will land if dropped - mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING); - } - - public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, Paint alphaClipPaint) { - final Canvas canvas = new Canvas(); - - int[] size = estimateItemSize(info.spanX, info.spanY, info, false); - - // The outline is used to visualize where the item will land if dropped - mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0], - size[1], alphaClipPaint); - } - - public void exitWidgetResizeMode() { - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.clearAllResizeFrames(); - } - - private void initAnimationArrays() { - final int childCount = getChildCount(); - if (mOldTranslationXs != null) return; - mOldTranslationXs = new float[childCount]; - mOldTranslationYs = new float[childCount]; - mOldScaleXs = new float[childCount]; - mOldScaleYs = new float[childCount]; - mOldBackgroundAlphas = new float[childCount]; - mOldAlphas = new float[childCount]; - mNewTranslationXs = new float[childCount]; - mNewTranslationYs = new float[childCount]; - mNewScaleXs = new float[childCount]; - mNewScaleYs = new float[childCount]; - mNewBackgroundAlphas = new float[childCount]; - mNewAlphas = new float[childCount]; - mNewRotationYs = new float[childCount]; - } - - Animator getChangeStateAnimation(final State state, boolean animated) { - return getChangeStateAnimation(state, animated, 0); - } - - Animator getChangeStateAnimation(final State state, boolean animated, int delay) { - if (mState == state) { - return null; - } - - // Initialize animation arrays for the first time if necessary - initAnimationArrays(); - - AnimatorSet anim = animated ? new AnimatorSet() : null; - - // Stop any scrolling, move to the current page right away - setCurrentPage(getNextPage()); - - final State oldState = mState; - final boolean oldStateIsNormal = (oldState == State.NORMAL); - final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED); - final boolean oldStateIsSmall = (oldState == State.SMALL); - mState = state; - final boolean stateIsNormal = (state == State.NORMAL); - final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED); - final boolean stateIsSmall = (state == State.SMALL); - float finalScaleFactor = 1.0f; - float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f; - float translationX = 0; - float translationY = 0; - boolean zoomIn = true; - - if (state != State.NORMAL) { - finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0); - setPageSpacing(mSpringLoadedPageSpacing); - if (oldStateIsNormal && stateIsSmall) { - zoomIn = false; - setLayoutScale(finalScaleFactor); - updateChildrenLayersEnabled(); - } else { - finalBackgroundAlpha = 1.0f; - setLayoutScale(finalScaleFactor); - } - } else { - setPageSpacing(PagedView.AUTOMATIC_PAGE_SPACING); - setLayoutScale(1.0f); - } - - final int duration = zoomIn ? - getResources().getInteger(R.integer.config_workspaceUnshrinkTime) : - getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); - for (int i = 0; i < getChildCount(); i++) { - final CellLayout cl = (CellLayout) getChildAt(i); - float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded || - (i == mCurrentPage)) ? 1f : 0f; - float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); - float initialAlpha = currentAlpha; - - // Determine the pages alpha during the state transition - if ((oldStateIsSmall && stateIsNormal) || - (oldStateIsNormal && stateIsSmall)) { - // To/from workspace - only show the current page unless the transition is not - // animated and the animation end callback below doesn't run; - // or, if we're in spring-loaded mode - if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) { - finalAlpha = 1f; - } else { - initialAlpha = 0f; - finalAlpha = 0f; - } - } - - mOldAlphas[i] = initialAlpha; - mNewAlphas[i] = finalAlpha; - if (animated) { - mOldTranslationXs[i] = cl.getTranslationX(); - mOldTranslationYs[i] = cl.getTranslationY(); - mOldScaleXs[i] = cl.getScaleX(); - mOldScaleYs[i] = cl.getScaleY(); - mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); - - mNewTranslationXs[i] = translationX; - mNewTranslationYs[i] = translationY; - mNewScaleXs[i] = finalScaleFactor; - mNewScaleYs[i] = finalScaleFactor; - mNewBackgroundAlphas[i] = finalBackgroundAlpha; - } else { - cl.setTranslationX(translationX); - cl.setTranslationY(translationY); - cl.setScaleX(finalScaleFactor); - cl.setScaleY(finalScaleFactor); - cl.setBackgroundAlpha(finalBackgroundAlpha); - cl.setShortcutAndWidgetAlpha(finalAlpha); - } - } - - if (animated) { - for (int index = 0; index < getChildCount(); index++) { - final int i = index; - final CellLayout cl = (CellLayout) getChildAt(i); - float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); - if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) { - cl.setTranslationX(mNewTranslationXs[i]); - cl.setTranslationY(mNewTranslationYs[i]); - cl.setScaleX(mNewScaleXs[i]); - cl.setScaleY(mNewScaleYs[i]); - cl.setBackgroundAlpha(mNewBackgroundAlphas[i]); - cl.setShortcutAndWidgetAlpha(mNewAlphas[i]); - cl.setRotationY(mNewRotationYs[i]); - } else { - LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl); - a.translationX(mNewTranslationXs[i]) - .translationY(mNewTranslationYs[i]) - .scaleX(mNewScaleXs[i]) - .scaleY(mNewScaleYs[i]) - .setDuration(duration) - .setInterpolator(mZoomInInterpolator); - anim.play(a); - - if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) { - LauncherViewPropertyAnimator alphaAnim = - new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets()); - alphaAnim.alpha(mNewAlphas[i]) - .setDuration(duration) - .setInterpolator(mZoomInInterpolator); - anim.play(alphaAnim); - } - if (mOldBackgroundAlphas[i] != 0 || - mNewBackgroundAlphas[i] != 0) { - ValueAnimator bgAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); - bgAnim.setInterpolator(mZoomInInterpolator); - bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { - public void onAnimationUpdate(float a, float b) { - cl.setBackgroundAlpha( - a * mOldBackgroundAlphas[i] + - b * mNewBackgroundAlphas[i]); - } - }); - anim.play(bgAnim); - } - } - } - buildPageHardwareLayers(); - anim.setStartDelay(delay); - } - - if (stateIsSpringLoaded) { - // Right now we're covered by Apps Customize - // Show the background gradient immediately, so the gradient will - // be showing once AppsCustomize disappears - animateBackgroundGradient(getResources().getInteger( - R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false); - } else { - // Fade the background gradient away - animateBackgroundGradient(0f, true); - } - return anim; - } - - @Override - public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { - mIsSwitchingState = true; - cancelScrollingIndicatorAnimations(); - } - - @Override - public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { - } - - @Override - public void onLauncherTransitionStep(Launcher l, float t) { - mTransitionProgress = t; - } - - @Override - public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { - mIsSwitchingState = false; - mWallpaperOffset.setOverrideHorizontalCatchupConstant(false); - updateChildrenLayersEnabled(); - // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure - // ensure that only the current page is visible during (and subsequently, after) the - // transition animation. If fade adjacent pages is disabled, then re-enable the page - // visibility after the transition animation. - if (!mWorkspaceFadeInAdjacentScreens) { - for (int i = 0; i < getChildCount(); i++) { - final CellLayout cl = (CellLayout) getChildAt(i); - cl.setShortcutAndWidgetAlpha(1f); - } - } - } - - @Override - public View getContent() { - return this; - } - - /** - * 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, boolean pruneToDrawable) { - final Rect clipRect = mTempRect; - v.getDrawingRect(clipRect); - - boolean textVisible = false; - - destCanvas.save(); - if (v instanceof TextView && pruneToDrawable) { - Drawable d = ((TextView) v).getCompoundDrawables()[1]; - clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding); - destCanvas.translate(padding / 2, padding / 2); - d.draw(destCanvas); - } else { - if (v instanceof FolderIcon) { - // For FolderIcons the text can bleed into the icon area, and so we need to - // hide the text completely (which can't be achieved by clipping). - if (((FolderIcon) v).getTextVisible()) { - ((FolderIcon) v).setTextVisible(false); - textVisible = true; - } - } else if (v instanceof BubbleTextView) { - final BubbleTextView tv = (BubbleTextView) v; - clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + - tv.getLayout().getLineTop(0); - } else if (v instanceof TextView) { - final TextView tv = (TextView) v; - clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() + - tv.getLayout().getLineTop(0); - } - destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); - destCanvas.clipRect(clipRect, Op.REPLACE); - v.draw(destCanvas); - - // Restore text visibility of FolderIcon if necessary - if (textVisible) { - ((FolderIcon) v).setTextVisible(true); - } - } - destCanvas.restore(); - } - - /** - * Returns a new bitmap to show when the given View is being dragged around. - * Responsibility for the bitmap is transferred to the caller. - */ - public Bitmap createDragBitmap(View v, Canvas canvas, int padding) { - Bitmap b; - - if (v instanceof TextView) { - Drawable d = ((TextView) v).getCompoundDrawables()[1]; - b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding, - d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888); - } else { - b = Bitmap.createBitmap( - v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); - } - - canvas.setBitmap(b); - drawDragView(v, canvas, padding, true); - canvas.setBitmap(null); - - return b; - } - - /** - * 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(android.R.color.holo_blue_light); - final Bitmap b = Bitmap.createBitmap( - v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); - - canvas.setBitmap(b); - drawDragView(v, canvas, padding, true); - mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); - canvas.setBitmap(null); - return b; - } - - /** - * 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(Bitmap orig, Canvas canvas, int padding, int w, int h, - Paint alphaClipPaint) { - final int outlineColor = getResources().getColor(android.R.color.holo_blue_light); - final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - canvas.setBitmap(b); - - Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); - float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), - (h - padding) / (float) orig.getHeight()); - int scaledWidth = (int) (scaleFactor * orig.getWidth()); - int scaledHeight = (int) (scaleFactor * orig.getHeight()); - Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); - - // center the image - dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); - - canvas.drawBitmap(orig, src, dst, null); - mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor, - alphaClipPaint); - canvas.setBitmap(null); - - 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; - child.setVisibility(INVISIBLE); - CellLayout layout = (CellLayout) child.getParent().getParent(); - layout.prepareChildForDrag(child); - - child.clearFocus(); - child.setPressed(false); - - final Canvas canvas = new Canvas(); - - // The outline is used to visualize where the item will land if dropped - mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); - beginDragShared(child, this); - } - - public void beginDragShared(View child, DragSource source) { - Resources r = getResources(); - - // The drag bitmap follows the touch point around on the screen - final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING); - - final int bmpWidth = b.getWidth(); - final int bmpHeight = b.getHeight(); - - mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); - int dragLayerX = - Math.round(mTempXY[0] - (bmpWidth - child.getScaleX() * child.getWidth()) / 2); - int dragLayerY = - Math.round(mTempXY[1] - (bmpHeight - child.getScaleY() * bmpHeight) / 2 - - DRAG_BITMAP_PADDING / 2); - - Point dragVisualizeOffset = null; - Rect dragRect = null; - if (child instanceof BubbleTextView || child instanceof PagedViewIcon) { - int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); - int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top); - int top = child.getPaddingTop(); - int left = (bmpWidth - iconSize) / 2; - int right = left + iconSize; - int bottom = top + iconSize; - dragLayerY += top; - // Note: The drag region is used to calculate drag layer offsets, but the - // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. - dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, - iconPaddingTop - DRAG_BITMAP_PADDING / 2); - dragRect = new Rect(left, top, right, bottom); - } else if (child instanceof FolderIcon) { - int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size); - dragRect = new Rect(0, 0, child.getWidth(), previewSize); - } - - // Clear the pressed state if necessary - if (child instanceof BubbleTextView) { - BubbleTextView icon = (BubbleTextView) child; - icon.clearPressedOrFocusedBackground(); - } - - mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, child.getScaleX()); - b.recycle(); - - // Show the scrolling indicator when you pick up an item - showScrollingIndicator(false); - } - - void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen, - int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) { - View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info); - - final int[] cellXY = new int[2]; - target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); - addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); - LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0], - cellXY[1]); - } - - public boolean transitionStateShouldAllowDrop() { - return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL); - } - - /** - * {@inheritDoc} - */ - public boolean acceptDrop(DragObject d) { - // If it's an external drop (e.g. from All Apps), check if it should be accepted - CellLayout dropTargetLayout = mDropToLayout; - if (d.dragSource != this) { - // Don't accept the drop if we're not over a screen at time of drop - if (dropTargetLayout == null) { - return false; - } - if (!transitionStateShouldAllowDrop()) return false; - - mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, - d.dragView, mDragViewVisualCenter); - - // We want the point to be mapped to the dragTarget. - if (mLauncher.isHotseatLayout(dropTargetLayout)) { - mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); - } else { - mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); - } - - int spanX = 1; - int spanY = 1; - if (mDragInfo != null) { - final CellLayout.CellInfo dragCellInfo = mDragInfo; - spanX = dragCellInfo.spanX; - spanY = dragCellInfo.spanY; - } else { - final ItemInfo dragInfo = (ItemInfo) d.dragInfo; - spanX = dragInfo.spanX; - spanY = dragInfo.spanY; - } - - int minSpanX = spanX; - int minSpanY = spanY; - if (d.dragInfo instanceof PendingAddWidgetInfo) { - minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; - minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; - } - - mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, - mTargetCell); - float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], - mDragViewVisualCenter[1], mTargetCell); - if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, - mTargetCell, distance, true)) { - return true; - } - if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, - mTargetCell, distance)) { - return true; - } - - int[] resultSpan = new int[2]; - mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, - null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); - boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; - - // Don't accept the drop if there's no room for the item - if (!foundCell) { - // Don't show the message if we are dropping on the AllApps button and the hotseat - // is full - boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout); - if (mTargetCell != null && isHotseat) { - Hotseat hotseat = mLauncher.getHotseat(); - if (hotseat.isAllAppsButtonRank( - hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) { - return false; - } - } - - mLauncher.showOutOfSpaceMessage(isHotseat); - return false; - } - } - return true; - } - - boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float - distance, boolean considerTimeout) { - if (distance > mMaxDistanceForFolderCreation) return false; - View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); - - if (dropOverView != null) { - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); - if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { - return false; - } - } - - boolean hasntMoved = false; - if (mDragInfo != null) { - hasntMoved = dropOverView == mDragInfo.cell; - } - - if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { - return false; - } - - boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); - boolean willBecomeShortcut = - (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); - - return (aboveShortcut && willBecomeShortcut); - } - - boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, - float distance) { - if (distance > mMaxDistanceForFolderCreation) return false; - View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); - - if (dropOverView != null) { - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); - if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { - return false; - } - } - - if (dropOverView instanceof FolderIcon) { - FolderIcon fi = (FolderIcon) dropOverView; - if (fi.acceptDrop(dragInfo)) { - return true; - } - } - return false; - } - - boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, - int[] targetCell, float distance, boolean external, DragView dragView, - Runnable postAnimationRunnable) { - if (distance > mMaxDistanceForFolderCreation) return false; - View v = target.getChildAt(targetCell[0], targetCell[1]); - - boolean hasntMoved = false; - if (mDragInfo != null) { - CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); - hasntMoved = (mDragInfo.cellX == targetCell[0] && - mDragInfo.cellY == targetCell[1]) && (cellParent == target); - } - - if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; - mCreateUserFolderOnDrop = false; - final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target); - - boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); - boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); - - if (aboveShortcut && willBecomeShortcut) { - ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); - ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); - // if the drag started here, we need to remove it from the workspace - if (!external) { - getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); - } - - Rect folderLocation = new Rect(); - float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); - target.removeView(v); - - FolderIcon fi = - mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]); - destInfo.cellX = -1; - destInfo.cellY = -1; - sourceInfo.cellX = -1; - sourceInfo.cellY = -1; - - // If the dragView is null, we can't animate - boolean animate = dragView != null; - if (animate) { - fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, - postAnimationRunnable); - } else { - fi.addItem(destInfo); - fi.addItem(sourceInfo); - } - return true; - } - return false; - } - - boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, - float distance, DragObject d, boolean external) { - if (distance > mMaxDistanceForFolderCreation) return false; - - View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); - if (!mAddToExistingFolderOnDrop) return false; - mAddToExistingFolderOnDrop = false; - - if (dropOverView instanceof FolderIcon) { - FolderIcon fi = (FolderIcon) dropOverView; - if (fi.acceptDrop(d.dragInfo)) { - fi.onDrop(d); - - // if the drag started here, we need to remove it from the workspace - if (!external) { - getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); - } - return true; - } - } - return false; - } - - public void onDrop(final DragObject d) { - mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, - mDragViewVisualCenter); - - CellLayout dropTargetLayout = mDropToLayout; - - // We want the point to be mapped to the dragTarget. - if (dropTargetLayout != null) { - if (mLauncher.isHotseatLayout(dropTargetLayout)) { - mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); - } else { - mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); - } - } - - int snapScreen = -1; - boolean resizeOnDrop = false; - if (d.dragSource != this) { - final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1] }; - onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); - } else if (mDragInfo != null) { - final View cell = mDragInfo.cell; - - Runnable resizeRunnable = null; - if (dropTargetLayout != null) { - // Move internally - boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); - boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); - long container = hasMovedIntoHotseat ? - LauncherSettings.Favorites.CONTAINER_HOTSEAT : - LauncherSettings.Favorites.CONTAINER_DESKTOP; - int screen = (mTargetCell[0] < 0) ? - mDragInfo.screen : indexOfChild(dropTargetLayout); - int spanX = mDragInfo != null ? mDragInfo.spanX : 1; - int spanY = mDragInfo != null ? mDragInfo.spanY : 1; - // First we find the cell nearest to point at which the item is - // dropped, without any consideration to whether there is an item there. - - mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) - mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); - float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], - mDragViewVisualCenter[1], mTargetCell); - - // If the item being dropped is a shortcut and the nearest drop - // cell also contains a shortcut, then create a folder with the two shortcuts. - if (!mInScrollArea && createUserFolderIfNecessary(cell, container, - dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { - return; - } - - if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, - distance, d, false)) { - return; - } - - // Aside from the special case where we're dropping a shortcut onto a shortcut, - // we need to find the nearest cell location that is vacant - ItemInfo item = (ItemInfo) d.dragInfo; - int minSpanX = item.spanX; - int minSpanY = item.spanY; - if (item.minSpanX > 0 && item.minSpanY > 0) { - minSpanX = item.minSpanX; - minSpanY = item.minSpanY; - } - - int[] resultSpan = new int[2]; - mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, - mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); - - boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; - if (foundCell && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { - resizeOnDrop = true; - item.spanX = resultSpan[0]; - item.spanY = resultSpan[1]; - AppWidgetHostView awhv = (AppWidgetHostView) cell; - AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], - resultSpan[1]); - } - - if (mCurrentPage != screen && !hasMovedIntoHotseat) { - snapScreen = screen; - snapToPage(screen); - } - - if (foundCell) { - final ItemInfo info = (ItemInfo) cell.getTag(); - if (hasMovedLayouts) { - // Reparent the view - getParentCellLayoutForView(cell).removeView(cell); - addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1], - info.spanX, info.spanY); - } - - // update the item's position after drop - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); - lp.cellX = lp.tmpCellX = mTargetCell[0]; - lp.cellY = lp.tmpCellY = mTargetCell[1]; - lp.cellHSpan = item.spanX; - lp.cellVSpan = item.spanY; - lp.isLockedToGrid = true; - cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen, - mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); - - if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && - cell instanceof LauncherAppWidgetHostView) { - final CellLayout cellLayout = dropTargetLayout; - // We post this call so that the widget has a chance to be placed - // in its final location - - final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; - AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); - if (pinfo != null && - pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { - final Runnable addResizeFrame = new Runnable() { - public void run() { - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.addResizeFrame(info, hostView, cellLayout); - } - }; - resizeRunnable = (new Runnable() { - public void run() { - if (!isPageMoving()) { - addResizeFrame.run(); - } else { - mDelayedResizeRunnable = addResizeFrame; - } - } - }); - } - } - - LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX, - lp.cellY); - } else { - // If we can't find a drop location, we return the item to its original position - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); - mTargetCell[0] = lp.cellX; - mTargetCell[1] = lp.cellY; - CellLayout layout = (CellLayout) cell.getParent().getParent(); - layout.markCellsAsOccupiedForView(cell); - } - } - - final CellLayout parent = (CellLayout) cell.getParent().getParent(); - final Runnable finalResizeRunnable = resizeRunnable; - // Prepare it to be animated into its new position - // This must be called after the view has been re-parented - final Runnable onCompleteRunnable = new Runnable() { - @Override - public void run() { - mAnimatingViewIntoPlace = false; - updateChildrenLayersEnabled(); - if (finalResizeRunnable != null) { - finalResizeRunnable.run(); - } - } - }; - mAnimatingViewIntoPlace = true; - if (d.dragView.hasDrawn()) { - final ItemInfo info = (ItemInfo) cell.getTag(); - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { - int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : - ANIMATE_INTO_POSITION_AND_DISAPPEAR; - animateWidgetDrop(info, parent, d.dragView, - onCompleteRunnable, animationType, cell, false); - } else { - int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, - onCompleteRunnable, this); - } - } else { - d.deferDragViewCleanupPostAnimation = false; - cell.setVisibility(VISIBLE); - } - parent.onDropChild(cell); - } - } - - public void setFinalScrollForPageChange(int screen) { - if (screen >= 0) { - mSavedScrollX = getScrollX(); - CellLayout cl = (CellLayout) getChildAt(screen); - mSavedTranslationX = cl.getTranslationX(); - mSavedRotationY = cl.getRotationY(); - final int newX = getChildOffset(screen) - getRelativeChildOffset(screen); - setScrollX(newX); - cl.setTranslationX(0f); - cl.setRotationY(0f); - } - } - - public void resetFinalScrollForPageChange(int screen) { - if (screen >= 0) { - CellLayout cl = (CellLayout) getChildAt(screen); - setScrollX(mSavedScrollX); - cl.setTranslationX(mSavedTranslationX); - cl.setRotationY(mSavedRotationY); - } - } - - public void getViewLocationRelativeToSelf(View v, int[] location) { - getLocationInWindow(location); - int x = location[0]; - int y = location[1]; - - v.getLocationInWindow(location); - int vX = location[0]; - int vY = location[1]; - - location[0] = vX - x; - location[1] = vY - y; - } - - public void onDragEnter(DragObject d) { - mDragEnforcer.onDragEnter(); - mCreateUserFolderOnDrop = false; - mAddToExistingFolderOnDrop = false; - - mDropToLayout = null; - CellLayout layout = getCurrentDropLayout(); - setCurrentDropLayout(layout); - setCurrentDragOverlappingLayout(layout); - - // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we - // don't need to show the outlines - if (LauncherApplication.isScreenLarge()) { - showOutlines(); - } - } - - static Rect getCellLayoutMetrics(Launcher launcher, int orientation) { - Resources res = launcher.getResources(); - Display display = launcher.getWindowManager().getDefaultDisplay(); - Point smallestSize = new Point(); - Point largestSize = new Point(); - display.getCurrentSizeRange(smallestSize, largestSize); - if (orientation == CellLayout.LANDSCAPE) { - if (mLandscapeCellLayoutMetrics == null) { - int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land); - int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land); - int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land); - int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land); - int width = largestSize.x - paddingLeft - paddingRight; - int height = smallestSize.y - paddingTop - paddingBottom; - mLandscapeCellLayoutMetrics = new Rect(); - CellLayout.getMetrics(mLandscapeCellLayoutMetrics, res, - width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(), - orientation); - } - return mLandscapeCellLayoutMetrics; - } else if (orientation == CellLayout.PORTRAIT) { - if (mPortraitCellLayoutMetrics == null) { - int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land); - int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land); - int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land); - int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land); - int width = smallestSize.x - paddingLeft - paddingRight; - int height = largestSize.y - paddingTop - paddingBottom; - mPortraitCellLayoutMetrics = new Rect(); - CellLayout.getMetrics(mPortraitCellLayoutMetrics, res, - width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(), - orientation); - } - return mPortraitCellLayoutMetrics; - } - return null; - } - - public void onDragExit(DragObject d) { - mDragEnforcer.onDragExit(); - - // Here we store the final page that will be dropped to, if the workspace in fact - // receives the drop - if (mInScrollArea) { - mDropToLayout = mDragOverlappingLayout; - } else { - mDropToLayout = mDragTargetLayout; - } - - if (mDragMode == DRAG_MODE_CREATE_FOLDER) { - mCreateUserFolderOnDrop = true; - } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { - mAddToExistingFolderOnDrop = true; - } - - // Reset the scroll area and previous drag target - onResetScrollArea(); - setCurrentDropLayout(null); - setCurrentDragOverlappingLayout(null); - - mSpringLoadedDragController.cancel(); - - if (!mIsPageMoving) { - hideOutlines(); - } - } - - void setCurrentDropLayout(CellLayout layout) { - if (mDragTargetLayout != null) { - mDragTargetLayout.revertTempState(); - mDragTargetLayout.onDragExit(); - } - mDragTargetLayout = layout; - if (mDragTargetLayout != null) { - mDragTargetLayout.onDragEnter(); - } - cleanupReorder(true); - cleanupFolderCreation(); - setCurrentDropOverCell(-1, -1); - } - - void setCurrentDragOverlappingLayout(CellLayout layout) { - if (mDragOverlappingLayout != null) { - mDragOverlappingLayout.setIsDragOverlapping(false); - } - mDragOverlappingLayout = layout; - if (mDragOverlappingLayout != null) { - mDragOverlappingLayout.setIsDragOverlapping(true); - } - invalidate(); - } - - void setCurrentDropOverCell(int x, int y) { - if (x != mDragOverX || y != mDragOverY) { - mDragOverX = x; - mDragOverY = y; - setDragMode(DRAG_MODE_NONE); - } - } - - void setDragMode(int dragMode) { - if (dragMode != mDragMode) { - if (dragMode == DRAG_MODE_NONE) { - cleanupAddToFolder(); - // We don't want to cancel the re-order alarm every time the target cell changes - // as this feels to slow / unresponsive. - cleanupReorder(false); - cleanupFolderCreation(); - } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { - cleanupReorder(true); - cleanupFolderCreation(); - } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { - cleanupAddToFolder(); - cleanupReorder(true); - } else if (dragMode == DRAG_MODE_REORDER) { - cleanupAddToFolder(); - cleanupFolderCreation(); - } - mDragMode = dragMode; - } - } - - private void cleanupFolderCreation() { - if (mDragFolderRingAnimator != null) { - mDragFolderRingAnimator.animateToNaturalState(); - } - mFolderCreationAlarm.cancelAlarm(); - } - - private void cleanupAddToFolder() { - if (mDragOverFolderIcon != null) { - mDragOverFolderIcon.onDragExit(null); - mDragOverFolderIcon = null; - } - } - - private void cleanupReorder(boolean cancelAlarm) { - // Any pending reorders are canceled - if (cancelAlarm) { - mReorderAlarm.cancelAlarm(); - } - mLastReorderX = -1; - mLastReorderY = -1; - } - - public DropTarget getDropTargetDelegate(DragObject d) { - return 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. - * - */ - 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; - } - int scrollX = getScrollX(); - if (mNextPage != INVALID_PAGE) { - scrollX = mScroller.getFinalX(); - } - xy[0] = xy[0] + scrollX - v.getLeft(); - xy[1] = xy[1] + getScrollY() - v.getTop(); - cachedInverseMatrix.mapPoints(xy); - } - - /* - * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace - * covers the full screen) - */ - void mapPointFromSelfToSibling(View v, float[] xy) { - xy[0] = xy[0] - v.getLeft(); - xy[1] = xy[1] - v.getTop(); - } - - void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { - xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft(); - xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop(); - } - - /* - * - * 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); - int scrollX = getScrollX(); - if (mNextPage != INVALID_PAGE) { - scrollX = mScroller.getFinalX(); - } - xy[0] -= (scrollX - v.getLeft()); - xy[1] -= (getScrollY() - 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; - draggedItemTopLeft[1] = dragViewY; - final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; - draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth(); - draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight(); - - // 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; - } - - /* - * - * 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, float originX, float originY, boolean exact) { - // 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 = {originX, originY}; - // 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 (!exact) { - // 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; - touchXy[1] = originY; - - // 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; - } - - // This is used to compute the visual center of the dragView. This point is then - // used to visualize drop locations and determine where to drop an item. The idea is that - // the visual center represents the user's interpretation of where the item is, and hence - // is the appropriate point to use when determining drop location. - private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, - DragView dragView, float[] recycle) { - float res[]; - if (recycle == null) { - res = new float[2]; - } else { - res = recycle; - } - - // First off, the drag view has been shifted in a way that is not represented in the - // x and y values or the x/yOffsets. Here we account for that shift. - x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX); - y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); - - // These represent the visual top and left of drag view if a dragRect was provided. - // If a dragRect was not provided, then they correspond to the actual view left and - // top, as the dragRect is in that case taken to be the entire dragView. - // R.dimen.dragViewOffsetY. - int left = x - xOffset; - int top = y - yOffset; - - // In order to find the visual center, we shift by half the dragRect - res[0] = left + dragView.getDragRegion().width() / 2; - res[1] = top + dragView.getDragRegion().height() / 2; - - return res; - } - - private boolean isDragWidget(DragObject d) { - return (d.dragInfo instanceof LauncherAppWidgetInfo || - d.dragInfo instanceof PendingAddWidgetInfo); - } - private boolean isExternalDragWidget(DragObject d) { - return d.dragSource != this && isDragWidget(d); - } - - public void onDragOver(DragObject d) { - // Skip drag over events while we are dragging over side pages - if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return; - - Rect r = new Rect(); - CellLayout layout = null; - ItemInfo item = (ItemInfo) d.dragInfo; - - // Ensure that we have proper spans for the item that we are dropping - if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); - mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, - d.dragView, mDragViewVisualCenter); - - final View child = (mDragInfo == null) ? null : mDragInfo.cell; - // Identify whether we have dragged over a side page - if (isSmall()) { - if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { - mLauncher.getHotseat().getHitRect(r); - if (r.contains(d.x, d.y)) { - layout = mLauncher.getHotseat().getLayout(); - } - } - if (layout == null) { - layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false); - } - if (layout != mDragTargetLayout) { - - setCurrentDropLayout(layout); - setCurrentDragOverlappingLayout(layout); - - boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); - if (isInSpringLoadedMode) { - if (mLauncher.isHotseatLayout(layout)) { - mSpringLoadedDragController.cancel(); - } else { - mSpringLoadedDragController.setAlarm(mDragTargetLayout); - } - } - } - } else { - // Test to see if we are over the hotseat otherwise just use the current page - if (mLauncher.getHotseat() != null && !isDragWidget(d)) { - mLauncher.getHotseat().getHitRect(r); - if (r.contains(d.x, d.y)) { - layout = mLauncher.getHotseat().getLayout(); - } - } - if (layout == null) { - layout = getCurrentDropLayout(); - } - if (layout != mDragTargetLayout) { - setCurrentDropLayout(layout); - setCurrentDragOverlappingLayout(layout); - } - } - - // Handle the drag over - if (mDragTargetLayout != null) { - // We want the point to be mapped to the dragTarget. - if (mLauncher.isHotseatLayout(mDragTargetLayout)) { - mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); - } else { - mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); - } - - ItemInfo info = (ItemInfo) d.dragInfo; - - mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], item.spanX, item.spanY, - mDragTargetLayout, mTargetCell); - - setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); - - float targetCellDistance = mDragTargetLayout.getDistanceFromCell( - mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - - final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], - mTargetCell[1]); - - manageFolderFeedback(info, mDragTargetLayout, mTargetCell, - targetCellDistance, dragOverView); - - int minSpanX = item.spanX; - int minSpanY = item.spanY; - if (item.minSpanX > 0 && item.minSpanY > 0) { - minSpanX = item.minSpanX; - minSpanY = item.minSpanY; - } - - boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) - mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, - item.spanY, child, mTargetCell); - - if (!nearestDropOccupied) { - mDragTargetLayout.visualizeDropLocation(child, mDragOutline, - (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], - mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, - d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion()); - } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) - && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] || - mLastReorderY != mTargetCell[1])) { - - // Otherwise, if we aren't adding to or creating a folder and there's no pending - // reorder, then we schedule a reorder - ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, - minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child); - mReorderAlarm.setOnAlarmListener(listener); - mReorderAlarm.setAlarm(REORDER_TIMEOUT); - } - - if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || - !nearestDropOccupied) { - if (mDragTargetLayout != null) { - mDragTargetLayout.revertTempState(); - } - } - } - } - - private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout, - int[] targetCell, float distance, View dragOverView) { - boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance, - false); - - if (mDragMode == DRAG_MODE_NONE && userFolderPending && - !mFolderCreationAlarm.alarmPending()) { - mFolderCreationAlarm.setOnAlarmListener(new - FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1])); - mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); - return; - } - - boolean willAddToFolder = - willAddToExistingUserFolder(info, targetLayout, targetCell, distance); - - if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { - mDragOverFolderIcon = ((FolderIcon) dragOverView); - mDragOverFolderIcon.onDragEnter(info); - if (targetLayout != null) { - targetLayout.clearDragOutlines(); - } - setDragMode(DRAG_MODE_ADD_TO_FOLDER); - return; - } - - if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { - setDragMode(DRAG_MODE_NONE); - } - if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { - setDragMode(DRAG_MODE_NONE); - } - - return; - } - - class FolderCreationAlarmListener implements OnAlarmListener { - CellLayout layout; - int cellX; - int cellY; - - public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { - this.layout = layout; - this.cellX = cellX; - this.cellY = cellY; - } - - public void onAlarm(Alarm alarm) { - if (mDragFolderRingAnimator == null) { - mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); - } - mDragFolderRingAnimator.setCell(cellX, cellY); - mDragFolderRingAnimator.setCellLayout(layout); - mDragFolderRingAnimator.animateToAcceptState(); - layout.showFolderAccept(mDragFolderRingAnimator); - layout.clearDragOutlines(); - setDragMode(DRAG_MODE_CREATE_FOLDER); - } - } - - class ReorderAlarmListener implements OnAlarmListener { - float[] dragViewCenter; - int minSpanX, minSpanY, spanX, spanY; - DragView dragView; - View child; - - public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, - int spanY, DragView dragView, View child) { - this.dragViewCenter = dragViewCenter; - this.minSpanX = minSpanX; - this.minSpanY = minSpanY; - this.spanX = spanX; - this.spanY = spanY; - this.child = child; - this.dragView = dragView; - } - - public void onAlarm(Alarm alarm) { - int[] resultSpan = new int[2]; - mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell); - mLastReorderX = mTargetCell[0]; - mLastReorderY = mTargetCell[1]; - - mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, - child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); - - if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { - mDragTargetLayout.revertTempState(); - } else { - setDragMode(DRAG_MODE_REORDER); - } - - boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; - mDragTargetLayout.visualizeDropLocation(child, mDragOutline, - (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], - mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, - dragView.getDragVisualizeOffset(), dragView.getDragRegion()); - } - } - - @Override - public void getHitRect(Rect outRect) { - // We want the workspace to have the whole area of the display (it will find the correct - // cell layout to drop to in the existing drag/drop logic. - outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); - } - - /** - * Add the item specified by dragInfo to the given layout. - * @return true if successful - */ - public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { - if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { - onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); - return true; - } - mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); - return false; - } - - private void onDropExternal(int[] touchXY, Object dragInfo, - CellLayout cellLayout, boolean insertAtFirst) { - onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); - } - - /** - * Drop an item that didn't originate on one of the workspace screens. - * It may have come from Launcher (e.g. from all apps or customize), or it may have - * come from another app altogether. - * - * 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(final int[] touchXY, final Object dragInfo, - final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { - final Runnable exitSpringLoadedRunnable = new Runnable() { - @Override - public void run() { - mLauncher.exitSpringLoadedDragModeDelayed(true, false, null); - } - }; - - ItemInfo info = (ItemInfo) dragInfo; - int spanX = info.spanX; - int spanY = info.spanY; - if (mDragInfo != null) { - spanX = mDragInfo.spanX; - spanY = mDragInfo.spanY; - } - - final long container = mLauncher.isHotseatLayout(cellLayout) ? - LauncherSettings.Favorites.CONTAINER_HOTSEAT : - LauncherSettings.Favorites.CONTAINER_DESKTOP; - final int screen = indexOfChild(cellLayout); - if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage - && mState != State.SPRING_LOADED) { - snapToPage(screen); - } - - if (info instanceof PendingAddItemInfo) { - final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; - - boolean findNearestVacantCell = true; - if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { - mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, - cellLayout, mTargetCell); - float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], - mDragViewVisualCenter[1], mTargetCell); - if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, - distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, - cellLayout, mTargetCell, distance)) { - findNearestVacantCell = false; - } - } - - final ItemInfo item = (ItemInfo) d.dragInfo; - if (findNearestVacantCell) { - int minSpanX = item.spanX; - int minSpanY = item.spanY; - if (item.minSpanX > 0 && item.minSpanY > 0) { - minSpanX = item.minSpanX; - minSpanY = item.minSpanY; - } - int[] resultSpan = new int[2]; - mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, - null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); - item.spanX = resultSpan[0]; - item.spanY = resultSpan[1]; - } - - Runnable onAnimationCompleteRunnable = new Runnable() { - @Override - public void run() { - // When dragging and dropping from customization tray, we deal with creating - // widgets/shortcuts/folders in a slightly different way - switch (pendingInfo.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - int span[] = new int[2]; - span[0] = item.spanX; - span[1] = item.spanY; - mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo, - container, screen, mTargetCell, span, null); - break; - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - mLauncher.processShortcutFromDrop(pendingInfo.componentName, - container, screen, mTargetCell, null); - break; - default: - throw new IllegalStateException("Unknown item type: " + - pendingInfo.itemType); - } - } - }; - View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET - ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; - int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; - if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && - ((PendingAddWidgetInfo) pendingInfo).info.configure != null) { - animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; - } - animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, - animationStyle, finalView, true); - } else { - // This is for other drag/drop cases, like dragging from All Apps - 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); - } - view = mLauncher.createShortcut(R.layout.application, cellLayout, - (ShortcutInfo) info); - break; - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, - (FolderInfo) info, mIconCache); - break; - default: - throw new IllegalStateException("Unknown item type: " + info.itemType); - } - - // First we find the cell nearest to point at which the item is - // dropped, without any consideration to whether there is an item there. - if (touchXY != null) { - mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, - cellLayout, mTargetCell); - float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], - mDragViewVisualCenter[1], mTargetCell); - d.postAnimationRunnable = exitSpringLoadedRunnable; - if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, - true, d.dragView, d.postAnimationRunnable)) { - return; - } - if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, - true)) { - return; - } - } - - if (touchXY != null) { - // when dragging and dropping, just find the closest free spot - mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], - (int) mDragViewVisualCenter[1], 1, 1, 1, 1, - null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); - } else { - cellLayout.findCellForSpan(mTargetCell, 1, 1); - } - addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX, - info.spanY, insertAtFirst); - cellLayout.onDropChild(view); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); - cellLayout.getShortcutsAndWidgets().measureChild(view); - - - LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, - lp.cellX, lp.cellY); - - if (d.dragView != null) { - // We wrap the animation call in the temporary set and reset of the current - // cellLayout to its final transform -- this means we animate the drag view to - // the correct final location. - setFinalTransitionTransform(cellLayout); - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, - exitSpringLoadedRunnable); - resetTransitionTransform(cellLayout); - } - } - } - - public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { - int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, - widgetInfo.spanY, widgetInfo, false); - int visibility = layout.getVisibility(); - layout.setVisibility(VISIBLE); - - int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); - int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); - Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], - Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - - layout.measure(width, height); - layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); - layout.draw(c); - c.setBitmap(null); - layout.setVisibility(visibility); - return b; - } - - private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, - DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, - boolean external, boolean scale) { - // Now we animate the dragView, (ie. the widget or shortcut preview) into its final - // location and size on the home screen. - int spanX = info.spanX; - int spanY = info.spanY; - - Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY); - loc[0] = r.left; - loc[1] = r.top; - - setFinalTransitionTransform(layout); - float cellLayoutScale = - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc); - resetTransitionTransform(layout); - - float dragViewScaleX; - float dragViewScaleY; - if (scale) { - dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); - dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); - } else { - dragViewScaleX = 1f; - dragViewScaleY = 1f; - } - - // The animation will scale the dragView about its center, so we need to center about - // the final location. - loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2; - loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; - - scaleXY[0] = dragViewScaleX * cellLayoutScale; - scaleXY[1] = dragViewScaleY * cellLayoutScale; - } - - public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView, - final Runnable onCompleteRunnable, int animationType, final View finalView, - boolean external) { - Rect from = new Rect(); - mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); - - int[] finalPos = new int[2]; - float scaleXY[] = new float[2]; - boolean scalePreview = !(info instanceof PendingAddShortcutInfo); - getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, - external, scalePreview); - - Resources res = mLauncher.getResources(); - int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; - - // In the case where we've prebound the widget, we remove it from the DragLayer - if (finalView instanceof AppWidgetHostView && external) { - Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView"); - mLauncher.getDragLayer().removeView(finalView); - } - if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { - Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); - dragView.setCrossFadeBitmap(crossFadeBitmap); - dragView.crossFade((int) (duration * 0.8f)); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) { - scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); - } - - DragLayer dragLayer = mLauncher.getDragLayer(); - if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { - mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, - DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); - } else { - int endStyle; - if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { - endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; - } else { - endStyle = DragLayer.ANIMATION_END_DISAPPEAR;; - } - - Runnable onComplete = new Runnable() { - @Override - public void run() { - if (finalView != null) { - finalView.setVisibility(VISIBLE); - } - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - } - }; - dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], - finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, - duration, this); - } - } - - public void setFinalTransitionTransform(CellLayout layout) { - if (isSwitchingState()) { - int index = indexOfChild(layout); - mCurrentScaleX = layout.getScaleX(); - mCurrentScaleY = layout.getScaleY(); - mCurrentTranslationX = layout.getTranslationX(); - mCurrentTranslationY = layout.getTranslationY(); - mCurrentRotationY = layout.getRotationY(); - layout.setScaleX(mNewScaleXs[index]); - layout.setScaleY(mNewScaleYs[index]); - layout.setTranslationX(mNewTranslationXs[index]); - layout.setTranslationY(mNewTranslationYs[index]); - layout.setRotationY(mNewRotationYs[index]); - } - } - public void resetTransitionTransform(CellLayout layout) { - if (isSwitchingState()) { - mCurrentScaleX = layout.getScaleX(); - mCurrentScaleY = layout.getScaleY(); - mCurrentTranslationX = layout.getTranslationX(); - mCurrentTranslationY = layout.getTranslationY(); - mCurrentRotationY = layout.getRotationY(); - layout.setScaleX(mCurrentScaleX); - layout.setScaleY(mCurrentScaleY); - layout.setTranslationX(mCurrentTranslationX); - layout.setTranslationY(mCurrentTranslationY); - layout.setRotationY(mCurrentRotationY); - } - } - - /** - * Return the current {@link CellLayout}, correctly picking the destination - * screen while a scroll is in progress. - */ - public CellLayout getCurrentDropLayout() { - return (CellLayout) getChildAt(getNextPage()); - } - - /** - * 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 CellLayout.CellInfo getDragInfo() { - return mDragInfo; - } - - /** - * Calculate the nearest cell where the given object would be dropped. - * - * pixelX and pixelY should be in the coordinate system of layout - */ - private int[] findNearestArea(int pixelX, int pixelY, - int spanX, int spanY, CellLayout layout, int[] recycle) { - return layout.findNearestArea( - pixelX, pixelY, spanX, spanY, recycle); - } - - void setup(DragController dragController) { - mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); - mDragController = dragController; - - // hardware layers on children are enabled on startup, but should be disabled until - // needed - updateChildrenLayersEnabled(); - setWallpaperDimension(); - } - - /** - * Called at the end of a drag which originated on the workspace. - */ - public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, - boolean success) { - if (success) { - if (target != this) { - if (mDragInfo != null) { - getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); - if (mDragInfo.cell instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget) mDragInfo.cell); - } - } - } - } else if (mDragInfo != null) { - CellLayout cellLayout; - if (mLauncher.isHotseatLayout(target)) { - cellLayout = mLauncher.getHotseat().getLayout(); - } else { - cellLayout = (CellLayout) getChildAt(mDragInfo.screen); - } - cellLayout.onDropChild(mDragInfo.cell); - } - if (d.cancelled && mDragInfo.cell != null) { - mDragInfo.cell.setVisibility(VISIBLE); - } - mDragOutline = null; - mDragInfo = null; - - // Hide the scrolling indicator after you pick up an item - hideScrollingIndicator(false); - } - - void updateItemLocationsInDatabase(CellLayout cl) { - int count = cl.getShortcutsAndWidgets().getChildCount(); - - int screen = indexOfChild(cl); - int container = Favorites.CONTAINER_DESKTOP; - - if (mLauncher.isHotseatLayout(cl)) { - screen = -1; - container = Favorites.CONTAINER_HOTSEAT; - } - - for (int i = 0; i < count; i++) { - View v = cl.getShortcutsAndWidgets().getChildAt(i); - ItemInfo info = (ItemInfo) v.getTag(); - // Null check required as the AllApps button doesn't have an item info - if (info != null) { - LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX, - info.cellY, info.spanX, info.spanY); - } - } - } - - @Override - public boolean supportsFlingToDelete() { - return true; - } - - @Override - public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { - // Do nothing - } - - @Override - public void onFlingToDeleteCompleted() { - // Do nothing - } - - public boolean isDropEnabled() { - return true; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - super.onRestoreInstanceState(state); - Launcher.setScreen(mCurrentPage); - } - - @Override - public void scrollLeft() { - if (!isSmall() && !mIsSwitchingState) { - super.scrollLeft(); - } - Folder openFolder = getOpenFolder(); - if (openFolder != null) { - openFolder.completeDragExit(); - } - } - - @Override - public void scrollRight() { - if (!isSmall() && !mIsSwitchingState) { - super.scrollRight(); - } - Folder openFolder = getOpenFolder(); - if (openFolder != null) { - openFolder.completeDragExit(); - } - } - - @Override - public boolean onEnterScrollArea(int x, int y, int direction) { - // Ignore the scroll area if we are dragging over the hot seat - boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext()); - if (mLauncher.getHotseat() != null && isPortrait) { - Rect r = new Rect(); - mLauncher.getHotseat().getHitRect(r); - if (r.contains(x, y)) { - return false; - } - } - - boolean result = false; - if (!isSmall() && !mIsSwitchingState) { - mInScrollArea = true; - - final int page = getNextPage() + - (direction == DragController.SCROLL_LEFT ? -1 : 1); - - // We always want to exit the current layout to ensure parity of enter / exit - setCurrentDropLayout(null); - - if (0 <= page && page < getChildCount()) { - CellLayout layout = (CellLayout) getChildAt(page); - setCurrentDragOverlappingLayout(layout); - - // Workspace is responsible for drawing the edge glow on adjacent pages, - // so we need to redraw the workspace when this may have changed. - invalidate(); - result = true; - } - } - return result; - } - - @Override - public boolean onExitScrollArea() { - boolean result = false; - if (mInScrollArea) { - invalidate(); - CellLayout layout = getCurrentDropLayout(); - setCurrentDropLayout(layout); - setCurrentDragOverlappingLayout(layout); - - result = true; - mInScrollArea = false; - } - return result; - } - - private void onResetScrollArea() { - setCurrentDragOverlappingLayout(null); - mInScrollArea = false; - } - - /** - * Returns a specific CellLayout - */ - CellLayout getParentCellLayoutForView(View v) { - ArrayList layouts = getWorkspaceAndHotseatCellLayouts(); - for (CellLayout layout : layouts) { - if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { - return layout; - } - } - return null; - } - - /** - * Returns a list of all the CellLayouts in the workspace. - */ - ArrayList getWorkspaceAndHotseatCellLayouts() { - ArrayList layouts = new ArrayList(); - int screenCount = getChildCount(); - for (int screen = 0; screen < screenCount; screen++) { - layouts.add(((CellLayout) getChildAt(screen))); - } - if (mLauncher.getHotseat() != null) { - layouts.add(mLauncher.getHotseat().getLayout()); - } - return layouts; - } - - /** - * We should only use this to search for specific children. Do not use this method to modify - * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from - * the hotseat and workspace pages - */ - ArrayList getAllShortcutAndWidgetContainers() { - ArrayList childrenLayouts = - new ArrayList(); - int screenCount = getChildCount(); - for (int screen = 0; screen < screenCount; screen++) { - childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); - } - if (mLauncher.getHotseat() != null) { - childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); - } - return childrenLayouts; - } - - public Folder getFolderForTag(Object tag) { - ArrayList childrenLayouts = - getAllShortcutAndWidgetContainers(); - for (ShortcutAndWidgetContainer layout: childrenLayouts) { - int count = layout.getChildCount(); - for (int i = 0; i < count; i++) { - View child = layout.getChildAt(i); - if (child instanceof Folder) { - Folder f = (Folder) child; - if (f.getInfo() == tag && f.getInfo().opened) { - return f; - } - } - } - } - return null; - } - - public View getViewForTag(Object tag) { - ArrayList childrenLayouts = - getAllShortcutAndWidgetContainers(); - for (ShortcutAndWidgetContainer layout: childrenLayouts) { - int count = layout.getChildCount(); - for (int i = 0; i < count; i++) { - View child = layout.getChildAt(i); - if (child.getTag() == tag) { - return child; - } - } - } - return null; - } - - void clearDropTargets() { - ArrayList childrenLayouts = - getAllShortcutAndWidgetContainers(); - for (ShortcutAndWidgetContainer layout: childrenLayouts) { - int childCount = layout.getChildCount(); - for (int j = 0; j < childCount; j++) { - View v = layout.getChildAt(j); - if (v instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget) v); - } - } - } - } - - void removeItems(final ArrayList apps) { - final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); - - final HashSet packageNames = new HashSet(); - final int appCount = apps.size(); - for (int i = 0; i < appCount; i++) { - packageNames.add(apps.get(i).componentName.getPackageName()); - } - - ArrayList cellLayouts = getWorkspaceAndHotseatCellLayouts(); - for (final CellLayout layoutParent: cellLayouts) { - final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); - - // Avoid ANRs by treating each screen separately - post(new Runnable() { - public void run() { - final ArrayList childrenToRemove = new ArrayList(); - 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 (name != null) { - if (packageNames.contains(name.getPackageName())) { - LauncherModel.deleteItemFromDatabase(mLauncher, info); - childrenToRemove.add(view); - } - } - } else if (tag instanceof FolderInfo) { - final FolderInfo info = (FolderInfo) tag; - final ArrayList contents = info.contents; - final int contentsCount = contents.size(); - final ArrayList appsToRemoveFromFolder = - new ArrayList(); - - for (int k = 0; k < contentsCount; k++) { - final ShortcutInfo appInfo = contents.get(k); - final Intent intent = appInfo.intent; - final ComponentName name = intent.getComponent(); - - if (name != null) { - if (packageNames.contains(name.getPackageName())) { - appsToRemoveFromFolder.add(appInfo); - } - } - } - for (ShortcutInfo item: appsToRemoveFromFolder) { - info.remove(item); - LauncherModel.deleteItemFromDatabase(mLauncher, item); - } - } else if (tag instanceof LauncherAppWidgetInfo) { - final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; - final ComponentName provider = info.providerName; - if (provider != null) { - if (packageNames.contains(provider.getPackageName())) { - LauncherModel.deleteItemFromDatabase(mLauncher, info); - childrenToRemove.add(view); - } - } - } - } - - childCount = childrenToRemove.size(); - for (int j = 0; j < childCount; j++) { - View child = childrenToRemove.get(j); - // Note: We can not remove the view directly from CellLayoutChildren as this - // does not re-mark the spaces as unoccupied. - layoutParent.removeViewInLayout(child); - if (child instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget)child); - } - } - - if (childCount > 0) { - layout.requestLayout(); - layout.invalidate(); - } - } - }); - } - - // It is no longer the case the BubbleTextViews correspond 1:1 with the workspace items in - // the database (and LauncherModel) since shortcuts are not added and animated in until - // the user returns to launcher. As a result, we really should be cleaning up the Db - // regardless of whether the item was added or not (unlike the logic above). This is only - // relevant for direct workspace items. - post(new Runnable() { - @Override - public void run() { - String spKey = LauncherApplication.getSharedPreferencesKey(); - SharedPreferences sp = getContext().getSharedPreferences(spKey, - Context.MODE_PRIVATE); - Set newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, - null); - - for (String packageName: packageNames) { - // Remove all items that have the same package, but were not removed above - ArrayList infos = - mLauncher.getModel().getShortcutInfosForPackage(packageName); - for (ShortcutInfo info : infos) { - LauncherModel.deleteItemFromDatabase(mLauncher, info); - } - // Remove all queued items that match the same package - if (newApps != null) { - synchronized (newApps) { - Iterator iter = newApps.iterator(); - while (iter.hasNext()) { - try { - Intent intent = Intent.parseUri(iter.next(), 0); - String pn = ItemInfo.getPackageName(intent); - if (packageNames.contains(pn)) { - iter.remove(); - } - } catch (URISyntaxException e) {} - } - } - } - } - } - }); - } - - void updateShortcuts(ArrayList apps) { - ArrayList childrenLayouts = getAllShortcutAndWidgetContainers(); - for (ShortcutAndWidgetContainer layout: childrenLayouts) { - 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) { - ShortcutInfo info = (ShortcutInfo) tag; - // We need to check for ACTION_MAIN otherwise getComponent() might - // return null for some shortcuts (for instance, for shortcuts to - // web pages.) - final Intent intent = info.intent; - final ComponentName name = intent.getComponent(); - if (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++) { - ApplicationInfo app = apps.get(k); - if (app.componentName.equals(name)) { - BubbleTextView shortcut = (BubbleTextView) view; - info.updateIcon(mIconCache); - info.title = app.title.toString(); - shortcut.applyFromShortcutInfo(info, mIconCache); - } - } - } - } - } - } - } - - void moveToDefaultScreen(boolean animate) { - if (!isSmall()) { - if (animate) { - snapToPage(mDefaultPage); - } else { - setCurrentPage(mDefaultPage); - } - } - getChildAt(mDefaultPage).requestFocus(); - } - - @Override - public void syncPages() { - } - - @Override - public void syncPageItems(int page, boolean immediate) { - } - - @Override - protected String getCurrentPageDescription() { - int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; - return String.format(getContext().getString(R.string.workspace_scroll_format), - page + 1, getChildCount()); - } - - public void getLocationInDragLayer(int[] loc) { - mLauncher.getDragLayer().getLocationInDragLayer(this, loc); - } - - void setFadeForOverScroll(float fade) { - if (!isScrollingIndicatorEnabled()) return; - - mOverscrollFade = fade; - float reducedFade = 0.5f + 0.5f * (1 - fade); - final ViewGroup parent = (ViewGroup) getParent(); - final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider)); - final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider)); - final View scrollIndicator = getScrollingIndicator(); - - cancelScrollingIndicatorAnimations(); - if (qsbDivider != null) qsbDivider.setAlpha(reducedFade); - if (dockDivider != null) dockDivider.setAlpha(reducedFade); - scrollIndicator.setAlpha(1 - fade); - } -} diff --git a/src/com/cyanogenmod/trebuchet/AccessibleTabView.java b/src/com/cyanogenmod/trebuchet/AccessibleTabView.java new file mode 100644 index 000000000..de5a6893f --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/AccessibleTabView.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.TextView; + +/** + * We use a custom tab view to process our own focus traversals. + */ +public class AccessibleTabView extends TextView { + public AccessibleTabView(Context context) { + super(context); + } + + public AccessibleTabView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AccessibleTabView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return FocusHelper.handleTabKeyEvent(this, keyCode, event) + || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return FocusHelper.handleTabKeyEvent(this, keyCode, event) + || super.onKeyUp(keyCode, event); + } +} diff --git a/src/com/cyanogenmod/trebuchet/AddAdapter.java b/src/com/cyanogenmod/trebuchet/AddAdapter.java new file mode 100644 index 000000000..5a3d43d05 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/AddAdapter.java @@ -0,0 +1,103 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import java.util.ArrayList; + +import com.cyanogenmod.trebuchet.R; + +/** + * Adapter showing the types of items that can be added to a {@link Workspace}. + */ +public class AddAdapter extends BaseAdapter { + + private final LayoutInflater mInflater; + + private final ArrayList mItems = new ArrayList(); + + public static final int ITEM_SHORTCUT = 0; + public static final int ITEM_APPWIDGET = 1; + public static final int ITEM_APPLICATION = 2; + public static final int ITEM_WALLPAPER = 3; + + /** + * Specific item in our list. + */ + public class ListItem { + public final CharSequence text; + public final Drawable image; + public final int actionTag; + + public ListItem(Resources res, int textResourceId, int imageResourceId, int actionTag) { + text = res.getString(textResourceId); + if (imageResourceId != -1) { + image = res.getDrawable(imageResourceId); + } else { + image = null; + } + this.actionTag = actionTag; + } + } + + public AddAdapter(Launcher launcher) { + super(); + + mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + // Create default actions + Resources res = launcher.getResources(); + + mItems.add(new ListItem(res, R.string.group_wallpapers, + R.drawable.ic_launcher_wallpaper, ITEM_WALLPAPER)); + } + + public View getView(int position, View convertView, ViewGroup parent) { + ListItem item = (ListItem) getItem(position); + + if (convertView == null) { + convertView = mInflater.inflate(R.layout.add_list_item, parent, false); + } + + TextView textView = (TextView) convertView; + textView.setTag(item); + textView.setText(item.text); + textView.setCompoundDrawablesWithIntrinsicBounds(item.image, null, null, null); + + return convertView; + } + + public int getCount() { + return mItems.size(); + } + + public Object getItem(int position) { + return mItems.get(position); + } + + public long getItemId(int position) { + return position; + } +} diff --git a/src/com/cyanogenmod/trebuchet/Alarm.java b/src/com/cyanogenmod/trebuchet/Alarm.java new file mode 100644 index 000000000..61efce42f --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Alarm.java @@ -0,0 +1,84 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.os.Handler; + +public class Alarm implements Runnable{ + // if we reach this time and the alarm hasn't been cancelled, call the listener + private long mAlarmTriggerTime; + + // if we've scheduled a call to run() (ie called mHandler.postDelayed), this variable is true. + // We use this to avoid having multiple pending callbacks + private boolean mWaitingForCallback; + + private Handler mHandler; + private OnAlarmListener mAlarmListener; + private boolean mAlarmPending = false; + + public Alarm() { + mHandler = new Handler(); + } + + public void setOnAlarmListener(OnAlarmListener alarmListener) { + mAlarmListener = alarmListener; + } + + // Sets the alarm to go off in a certain number of milliseconds. If the alarm is already set, + // it's overwritten and only the new alarm setting is used + public void setAlarm(long millisecondsInFuture) { + long currentTime = System.currentTimeMillis(); + mAlarmPending = true; + mAlarmTriggerTime = currentTime + millisecondsInFuture; + if (!mWaitingForCallback) { + mHandler.postDelayed(this, mAlarmTriggerTime - currentTime); + mWaitingForCallback = true; + } + } + + public void cancelAlarm() { + mAlarmTriggerTime = 0; + mAlarmPending = false; + } + + // this is called when our timer runs out + public void run() { + mWaitingForCallback = false; + if (mAlarmTriggerTime != 0) { + long currentTime = System.currentTimeMillis(); + if (mAlarmTriggerTime > currentTime) { + // We still need to wait some time to trigger spring loaded mode-- + // post a new callback + mHandler.postDelayed(this, Math.max(0, mAlarmTriggerTime - currentTime)); + mWaitingForCallback = true; + } else { + mAlarmPending = false; + if (mAlarmListener != null) { + mAlarmListener.onAlarm(this); + } + } + } + } + + public boolean alarmPending() { + return mAlarmPending; + } +} + +interface OnAlarmListener { + public void onAlarm(Alarm alarm); +} diff --git a/src/com/cyanogenmod/trebuchet/AllAppsList.java b/src/com/cyanogenmod/trebuchet/AllAppsList.java new file mode 100644 index 000000000..cc0e8cfd5 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/AllAppsList.java @@ -0,0 +1,221 @@ +/* + * 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.cyanogenmod.trebuchet; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; + + +/** + * Stores the list of all applications for the all apps view. + */ +class AllAppsList { + public static final int DEFAULT_APPLICATIONS_NUMBER = 42; + + /** The list off all apps. */ + public ArrayList data = + new ArrayList(DEFAULT_APPLICATIONS_NUMBER); + /** The list of apps that have been added since the last notify() call. */ + public ArrayList added = + new ArrayList(DEFAULT_APPLICATIONS_NUMBER); + /** The list of apps that have been removed since the last notify() call. */ + public ArrayList removed = new ArrayList(); + /** The list of apps that have been modified since the last notify() call. */ + public ArrayList modified = new ArrayList(); + + private IconCache mIconCache; + + /** + * Boring constructor. + */ + public AllAppsList(IconCache iconCache) { + mIconCache = iconCache; + } + + /** + * Add the supplied ApplicationInfo objects to the list, and enqueue it into the + * list to broadcast when notify() is called. + * + * If the app is already in the list, doesn't add it. + */ + public void add(ApplicationInfo info) { + if (findActivity(data, info.componentName)) { + return; + } + data.add(info); + added.add(info); + } + + public void clear() { + data.clear(); + // TODO: do we clear these too? + added.clear(); + removed.clear(); + modified.clear(); + } + + public int size() { + return data.size(); + } + + public ApplicationInfo get(int index) { + return data.get(index); + } + + /** + * Add the icons for the supplied apk called packageName. + */ + public void addPackage(Context context, String packageName) { + final List matches = findActivitiesForPackage(context, packageName); + + if (matches.size() > 0) { + for (ResolveInfo info : matches) { + add(new ApplicationInfo(context.getPackageManager(), info, mIconCache, null)); + } + } + } + + /** + * Remove the apps for the given apk identified by packageName. + */ + public void removePackage(String packageName) { + final List data = this.data; + for (int i = data.size() - 1; i >= 0; i--) { + ApplicationInfo info = data.get(i); + final ComponentName component = info.intent.getComponent(); + if (packageName.equals(component.getPackageName())) { + removed.add(info); + data.remove(i); + } + } + // This is more aggressive than it needs to be. + mIconCache.flush(); + } + + /** + * Add and remove icons for this package which has been updated. + */ + public void updatePackage(Context context, String packageName) { + final List matches = findActivitiesForPackage(context, packageName); + if (matches.size() > 0) { + // Find disabled/removed activities and remove them from data and add them + // to the removed list. + for (int i = data.size() - 1; i >= 0; i--) { + final ApplicationInfo applicationInfo = data.get(i); + final ComponentName component = applicationInfo.intent.getComponent(); + if (packageName.equals(component.getPackageName())) { + if (!findActivity(matches, component)) { + removed.add(applicationInfo); + mIconCache.remove(component); + data.remove(i); + } + } + } + + // Find enabled activities and add them to the adapter + // Also updates existing activities with new labels/icons + int count = matches.size(); + for (int i = 0; i < count; i++) { + final ResolveInfo info = matches.get(i); + ApplicationInfo applicationInfo = findApplicationInfoLocked( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + if (applicationInfo == null) { + add(new ApplicationInfo(context.getPackageManager(), info, mIconCache, null)); + } else { + mIconCache.remove(applicationInfo.componentName); + mIconCache.getTitleAndIcon(applicationInfo, info, null); + modified.add(applicationInfo); + } + } + } else { + // Remove all data for this package. + for (int i = data.size() - 1; i >= 0; i--) { + final ApplicationInfo applicationInfo = data.get(i); + final ComponentName component = applicationInfo.intent.getComponent(); + if (packageName.equals(component.getPackageName())) { + removed.add(applicationInfo); + mIconCache.remove(component); + data.remove(i); + } + } + } + } + + /** + * Query the package manager for MAIN/LAUNCHER activities in the supplied package. + */ + private static List findActivitiesForPackage(Context context, String packageName) { + final PackageManager packageManager = context.getPackageManager(); + + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + mainIntent.setPackage(packageName); + + final List apps = packageManager.queryIntentActivities(mainIntent, 0); + return apps != null ? apps : new ArrayList(); + } + + /** + * Returns whether apps contains component. + */ + private static boolean findActivity(List apps, ComponentName component) { + final String className = component.getClassName(); + for (ResolveInfo info : apps) { + final ActivityInfo activityInfo = info.activityInfo; + if (activityInfo.name.equals(className)) { + return true; + } + } + return false; + } + + /** + * Returns whether apps contains component. + */ + private static boolean findActivity(ArrayList apps, ComponentName component) { + final int N = apps.size(); + for (int i=0; i list); + + public void addApps(ArrayList list); + + public void removeApps(ArrayList list); + + public void updateApps(ArrayList list); + + // Resets the AllApps page to the front + public void reset(); + + public void dumpState(); + + public void surrender(); +} diff --git a/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java b/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java new file mode 100644 index 000000000..fdd8b3edd --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java @@ -0,0 +1,429 @@ +package com.cyanogenmod.trebuchet; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.graphics.Rect; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.cyanogenmod.trebuchet.R; + +public class AppWidgetResizeFrame extends FrameLayout { + private LauncherAppWidgetHostView mWidgetView; + private CellLayout mCellLayout; + private DragLayer mDragLayer; + private Workspace mWorkspace; + private ImageView mLeftHandle; + private ImageView mRightHandle; + private ImageView mTopHandle; + private ImageView mBottomHandle; + + private boolean mLeftBorderActive; + private boolean mRightBorderActive; + private boolean mTopBorderActive; + private boolean mBottomBorderActive; + + private int mWidgetPaddingLeft; + private int mWidgetPaddingRight; + private int mWidgetPaddingTop; + private int mWidgetPaddingBottom; + + private int mBaselineWidth; + private int mBaselineHeight; + private int mBaselineX; + private int mBaselineY; + private int mResizeMode; + + private int mRunningHInc; + private int mRunningVInc; + private int mMinHSpan; + private int mMinVSpan; + private int mDeltaX; + private int mDeltaY; + private int mDeltaXAddOn; + private int mDeltaYAddOn; + + private int mBackgroundPadding; + private int mTouchTargetWidth; + + int[] mDirectionVector = new int[2]; + + final int SNAP_DURATION = 150; + final int BACKGROUND_PADDING = 24; + final float DIMMED_HANDLE_ALPHA = 0f; + final float RESIZE_THRESHOLD = 0.66f; + + public static final int LEFT = 0; + public static final int TOP = 1; + public static final int RIGHT = 2; + public static final int BOTTOM = 3; + + private Launcher mLauncher; + + public AppWidgetResizeFrame(Context context, + LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { + + super(context); + mLauncher = (Launcher) context; + mCellLayout = cellLayout; + mWidgetView = widgetView; + mResizeMode = widgetView.getAppWidgetInfo().resizeMode; + mDragLayer = dragLayer; + mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); + + final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo(); + int[] result = Launcher.getMinSpanForWidget(mLauncher, info); + mMinHSpan = result[0]; + mMinVSpan = result[1]; + + setBackgroundResource(R.drawable.widget_resize_frame_holo); + setPadding(0, 0, 0, 0); + + LayoutParams lp; + mLeftHandle = new ImageView(context); + mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left); + lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, + Gravity.LEFT | Gravity.CENTER_VERTICAL); + addView(mLeftHandle, lp); + + mRightHandle = new ImageView(context); + mRightHandle.setImageResource(R.drawable.widget_resize_handle_right); + lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, + Gravity.RIGHT | Gravity.CENTER_VERTICAL); + addView(mRightHandle, lp); + + mTopHandle = new ImageView(context); + mTopHandle.setImageResource(R.drawable.widget_resize_handle_top); + lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL | Gravity.TOP); + addView(mTopHandle, lp); + + mBottomHandle = new ImageView(context); + mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom); + lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + addView(mBottomHandle, lp); + + Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context, + widgetView.getAppWidgetInfo().provider, null); + mWidgetPaddingLeft = p.left; + mWidgetPaddingTop = p.top; + mWidgetPaddingRight = p.right; + mWidgetPaddingBottom = p.bottom; + + if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { + mTopHandle.setVisibility(GONE); + mBottomHandle.setVisibility(GONE); + } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { + mLeftHandle.setVisibility(GONE); + mRightHandle.setVisibility(GONE); + } + + final float density = mLauncher.getResources().getDisplayMetrics().density; + mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING); + mTouchTargetWidth = 2 * mBackgroundPadding; + + // When we create the resize frame, we first mark all cells as unoccupied. The appropriate + // cells (same if not resized, or different) will be marked as occupied when the resize + // frame is dismissed. + mCellLayout.markCellsAsUnoccupiedForView(mWidgetView); + } + + public boolean beginResizeIfPointInRegion(int x, int y) { + boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; + boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; + mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive; + mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive; + mTopBorderActive = (y < mTouchTargetWidth) && verticalActive; + mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive; + + boolean anyBordersActive = mLeftBorderActive || mRightBorderActive + || mTopBorderActive || mBottomBorderActive; + + mBaselineWidth = getMeasuredWidth(); + mBaselineHeight = getMeasuredHeight(); + mBaselineX = getLeft(); + mBaselineY = getTop(); + + if (anyBordersActive) { + mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); + mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA); + mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); + mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); + } + return anyBordersActive; + } + + /** + * Here we bound the deltas such that the frame cannot be stretched beyond the extents + * of the CellLayout, and such that the frame's borders can't cross. + */ + public void updateDeltas(int deltaX, int deltaY) { + if (mLeftBorderActive) { + mDeltaX = Math.max(-mBaselineX, deltaX); + mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX); + } else if (mRightBorderActive) { + mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX); + mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX); + } + + if (mTopBorderActive) { + mDeltaY = Math.max(-mBaselineY, deltaY); + mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY); + } else if (mBottomBorderActive) { + mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY); + mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY); + } + } + + public void visualizeResizeForDelta(int deltaX, int deltaY) { + visualizeResizeForDelta(deltaX, deltaY, false); + } + + /** + * Based on the deltas, we resize the frame, and, if needed, we resize the widget. + */ + private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) { + updateDeltas(deltaX, deltaY); + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + + if (mLeftBorderActive) { + lp.x = mBaselineX + mDeltaX; + lp.width = mBaselineWidth - mDeltaX; + } else if (mRightBorderActive) { + lp.width = mBaselineWidth + mDeltaX; + } + + if (mTopBorderActive) { + lp.y = mBaselineY + mDeltaY; + lp.height = mBaselineHeight - mDeltaY; + } else if (mBottomBorderActive) { + lp.height = mBaselineHeight + mDeltaY; + } + + resizeWidgetIfNeeded(onDismiss); + requestLayout(); + } + + /** + * Based on the current deltas, we determine if and how to resize the widget. + */ + private void resizeWidgetIfNeeded(boolean onDismiss) { + int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); + int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); + + int deltaX = mDeltaX + mDeltaXAddOn; + int deltaY = mDeltaY + mDeltaYAddOn; + + float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc; + float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc; + + int hSpanInc = 0; + int vSpanInc = 0; + int cellXInc = 0; + int cellYInc = 0; + + int countX = mCellLayout.getCountX(); + int countY = mCellLayout.getCountY(); + + if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) { + hSpanInc = Math.round(hSpanIncF); + } + if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) { + vSpanInc = Math.round(vSpanIncF); + } + + if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return; + + + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); + + int spanX = lp.cellHSpan; + int spanY = lp.cellVSpan; + int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX; + int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY; + + int hSpanDelta = 0; + int vSpanDelta = 0; + + // For each border, we bound the resizing based on the minimum width, and the maximum + // expandability. + if (mLeftBorderActive) { + cellXInc = Math.max(-cellX, hSpanInc); + cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc); + hSpanInc *= -1; + hSpanInc = Math.min(cellX, hSpanInc); + hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); + hSpanDelta = -hSpanInc; + + } else if (mRightBorderActive) { + hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc); + hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); + hSpanDelta = hSpanInc; + } + + if (mTopBorderActive) { + cellYInc = Math.max(-cellY, vSpanInc); + cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc); + vSpanInc *= -1; + vSpanInc = Math.min(cellY, vSpanInc); + vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); + vSpanDelta = -vSpanInc; + } else if (mBottomBorderActive) { + vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc); + vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); + vSpanDelta = vSpanInc; + } + + mDirectionVector[0] = 0; + mDirectionVector[1] = 0; + // Update the widget's dimensions and position according to the deltas computed above + if (mLeftBorderActive || mRightBorderActive) { + spanX += hSpanInc; + cellX += cellXInc; + mDirectionVector[0] = mLeftBorderActive ? -1 : 1; + } + + if (mTopBorderActive || mBottomBorderActive) { + spanY += vSpanInc; + cellY += cellYInc; + mDirectionVector[1] = mTopBorderActive ? -1 : 1; + } + + if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return; + + if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView, + mDirectionVector, onDismiss)) { + lp.tmpCellX = cellX; + lp.tmpCellY = cellY; + lp.cellHSpan = spanX; + lp.cellVSpan = spanY; + mRunningVInc += vSpanDelta; + mRunningHInc += hSpanDelta; + if (!onDismiss) { + updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY); + } + } + mWidgetView.requestLayout(); + } + + static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher, + int spanX, int spanY) { + Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE); + Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT); + final float density = launcher.getResources().getDisplayMetrics().density; + + // Compute landscape size + int cellWidth = landMetrics.left; + int cellHeight = landMetrics.top; + int widthGap = landMetrics.right; + int heightGap = landMetrics.bottom; + int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); + int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); + + // Compute portrait size + cellWidth = portMetrics.left; + cellHeight = portMetrics.top; + widthGap = portMetrics.right; + heightGap = portMetrics.bottom; + int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); + int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); + + widgetView.updateAppWidgetSize(null, portWidth, landHeight, landWidth, portHeight); + } + + /** + * This is the final step of the resize. Here we save the new widget size and position + * to LauncherModel and animate the resize frame. + */ + public void commitResize() { + resizeWidgetIfNeeded(true); + requestLayout(); + } + + public void onTouchUp() { + int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); + int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); + + mDeltaXAddOn = mRunningHInc * xThreshold; + mDeltaYAddOn = mRunningVInc * yThreshold; + mDeltaX = 0; + mDeltaY = 0; + + post(new Runnable() { + @Override + public void run() { + snapToWidget(true); + } + }); + } + + public void snapToWidget(boolean animate) { + final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX(); + int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY(); + + int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft - + mWidgetPaddingRight; + int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop - + mWidgetPaddingBottom; + + int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft; + int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop; + + // We need to make sure the frame stays within the bounds of the CellLayout + if (newY < 0) { + newHeight -= -newY; + newY = 0; + } + if (newY + newHeight > mDragLayer.getHeight()) { + newHeight -= newY + newHeight - mDragLayer.getHeight(); + } + + if (!animate) { + lp.width = newWidth; + lp.height = newHeight; + lp.x = newX; + lp.y = newY; + mLeftHandle.setAlpha(1.0f); + mRightHandle.setAlpha(1.0f); + mTopHandle.setAlpha(1.0f); + mBottomHandle.setAlpha(1.0f); + requestLayout(); + } else { + PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth); + PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height, + newHeight); + PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX); + PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); + ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); + ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f); + ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f); + ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f); + ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f); + oa.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + requestLayout(); + } + }); + AnimatorSet set = new AnimatorSet(); + if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { + set.playTogether(oa, topOa, bottomOa); + } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { + set.playTogether(oa, leftOa, rightOa); + } else { + set.playTogether(oa, leftOa, rightOa, topOa, bottomOa); + } + + set.setDuration(SNAP_DURATION); + set.start(); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/ApplicationInfo.java b/src/com/cyanogenmod/trebuchet/ApplicationInfo.java new file mode 100644 index 000000000..ffc7a5c53 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/ApplicationInfo.java @@ -0,0 +1,144 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Represents an app in AllAppsView. + */ +class ApplicationInfo extends ItemInfo { + private static final String TAG = "Launcher2.ApplicationInfo"; + + /** + * The application name. + */ + CharSequence title; + + /** + * The intent used to start the application. + */ + Intent intent; + + /** + * A bitmap version of the application icon. + */ + Bitmap iconBitmap; + + /** + * The time at which the app was first installed. + */ + long firstInstallTime; + + ComponentName componentName; + + static final int DOWNLOADED_FLAG = 1; + static final int UPDATED_SYSTEM_APP_FLAG = 2; + + int flags = 0; + + ApplicationInfo() { + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; + } + + /** + * Must not hold the Context. + */ + public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache, + HashMap labelCache) { + 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 |= UPDATED_SYSTEM_APP_FLAG; + } + } + firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime; + } catch (NameNotFoundException e) { + Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName); + } + + iconCache.getTitleAndIcon(this, info, labelCache); + } + + public ApplicationInfo(ApplicationInfo info) { + super(info); + componentName = info.componentName; + title = info.title.toString(); + intent = new Intent(info.intent); + flags = info.flags; + firstInstallTime = info.firstInstallTime; + } + + /** Returns the package name that the shortcut's intent will resolve to, or an empty string if + * none exists. */ + String getPackageName() { + return super.getPackageName(intent); + } + + /** + * Creates the application intent based on a component name and various launch flags. + * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}. + * + * @param className the class name of the component representing the intent + * @param launchFlags the launch flags + */ + final void setActivity(ComponentName className, int launchFlags) { + intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(className); + intent.setFlags(launchFlags); + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION; + } + + @Override + public String toString() { + return "ApplicationInfo(title=" + title.toString() + ")"; + } + + public static void dumpApplicationInfoList(String tag, String label, + ArrayList list) { + Log.d(tag, label + " size=" + list.size()); + for (ApplicationInfo info: list) { + Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + + info.iconBitmap + " firstInstallTime=" + + info.firstInstallTime); + } + } + + public ShortcutInfo makeShortcut() { + return new ShortcutInfo(this); + } +} diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java new file mode 100644 index 000000000..5a58a9a95 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java @@ -0,0 +1,1899 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.appwidget.AppWidgetHostView; +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.Configuration; +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.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Insets; +import android.graphics.MaskFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.TableMaskFilter; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Process; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.GridLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.DropTarget.DragObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.lang.ref.WeakReference; + +/** + * A simple callback interface which also provides the results of the task. + */ +interface AsyncTaskCallback { + void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data); +} + +/** + * The data needed to perform either of the custom AsyncTasks. + */ +class AsyncTaskPageData { + enum Type { + LoadWidgetPreviewData + } + + AsyncTaskPageData(int p, ArrayList l, ArrayList si, AsyncTaskCallback bgR, + AsyncTaskCallback postR) { + page = p; + items = l; + sourceImages = si; + generatedImages = new ArrayList(); + maxImageWidth = maxImageHeight = -1; + doInBackgroundCallback = bgR; + postExecuteCallback = postR; + } + AsyncTaskPageData(int p, ArrayList l, int cw, int ch, AsyncTaskCallback bgR, + AsyncTaskCallback postR) { + page = p; + items = l; + generatedImages = new ArrayList(); + maxImageWidth = cw; + maxImageHeight = ch; + doInBackgroundCallback = bgR; + postExecuteCallback = postR; + } + void cleanup(boolean cancelled) { + // Clean up any references to source/generated bitmaps + if (sourceImages != null) { + if (cancelled) { + for (Bitmap b : sourceImages) { + b.recycle(); + } + } + sourceImages.clear(); + } + if (generatedImages != null) { + if (cancelled) { + for (Bitmap b : generatedImages) { + b.recycle(); + } + } + generatedImages.clear(); + } + } + int page; + ArrayList items; + ArrayList sourceImages; + ArrayList generatedImages; + int maxImageWidth; + int maxImageHeight; + AsyncTaskCallback doInBackgroundCallback; + AsyncTaskCallback postExecuteCallback; +} + +/** + * A generic template for an async task used in AppsCustomize. + */ +class AppsCustomizeAsyncTask extends AsyncTask { + AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) { + page = p; + threadPriority = Process.THREAD_PRIORITY_DEFAULT; + dataType = ty; + } + @Override + protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) { + if (params.length != 1) return null; + // Load each of the widget previews in the background + params[0].doInBackgroundCallback.run(this, params[0]); + return params[0]; + } + @Override + protected void onPostExecute(AsyncTaskPageData result) { + // All the widget previews are loaded, so we can just callback to inflate the page + result.postExecuteCallback.run(this, result); + } + + void setThreadPriority(int p) { + threadPriority = p; + } + void syncThreadPriority() { + Process.setThreadPriority(threadPriority); + } + + // The page that this async task is associated with + AsyncTaskPageData.Type dataType; + int page; + int threadPriority; +} + +abstract class WeakReferenceThreadLocal { + private ThreadLocal> mThreadLocal; + public WeakReferenceThreadLocal() { + mThreadLocal = new ThreadLocal>(); + } + + abstract T initialValue(); + + public void set(T t) { + mThreadLocal.set(new WeakReference(t)); + } + + public T get() { + WeakReference reference = mThreadLocal.get(); + T obj; + if (reference == null) { + obj = initialValue(); + mThreadLocal.set(new WeakReference(obj)); + return obj; + } else { + obj = reference.get(); + if (obj == null) { + obj = initialValue(); + mThreadLocal.set(new WeakReference(obj)); + } + return obj; + } + } +} + +class CanvasCache extends WeakReferenceThreadLocal { + @Override + protected Canvas initialValue() { + return new Canvas(); + } +} + +class PaintCache extends WeakReferenceThreadLocal { + @Override + protected Paint initialValue() { + return null; + } +} + +class BitmapCache extends WeakReferenceThreadLocal { + @Override + protected Bitmap initialValue() { + return null; + } +} + +class RectCache extends WeakReferenceThreadLocal { + @Override + protected Rect initialValue() { + return new Rect(); + } +} + +/** + * The Apps/Customize page that displays all the applications, widgets, and shortcuts. + */ +public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements + AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource, + PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener, + LauncherTransitionable { + static final String TAG = "AppsCustomizePagedView"; + + /** + * The different content types that this paged view can show. + */ + public enum ContentType { + Applications, + Widgets + } + + // Refs + private Launcher mLauncher; + private DragController mDragController; + private final LayoutInflater mLayoutInflater; + private final PackageManager mPackageManager; + + // Save and Restore + private int mSaveInstanceStateItemIndex = -1; + private PagedViewIcon mPressedIcon; + + // Content + private ArrayList mApps; + private ArrayList mWidgets; + + // Cling + private boolean mHasShownAllAppsCling; + private int mClingFocusedX; + private int mClingFocusedY; + + // Caching + private Canvas mCanvas; + private Drawable mDefaultWidgetBackground; + private IconCache mIconCache; + + // Dimens + private int mContentWidth; + private int mAppIconSize; + private int mMaxAppCellCountX, mMaxAppCellCountY; + private int mWidgetCountX, mWidgetCountY; + private int mWidgetWidthGap, mWidgetHeightGap; + private final float sWidgetPreviewIconPaddingPercentage = 0.25f; + private PagedViewCellLayout mWidgetSpacingLayout; + private int mNumAppsPages; + private int mNumWidgetPages; + + // Relating to the scroll and overscroll effects + Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f); + private static float CAMERA_DISTANCE = 6500; + private static float TRANSITION_SCALE_FACTOR = 0.74f; + private static float TRANSITION_PIVOT = 0.65f; + private static float TRANSITION_MAX_ROTATION = 22; + private static final boolean PERFORM_OVERSCROLL_ROTATION = true; + private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); + private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); + + // Previews & outlines + ArrayList mRunningTasks; + private static final int sPageSleepDelay = 200; + + private Runnable mInflateWidgetRunnable = null; + private Runnable mBindWidgetRunnable = null; + static final int WIDGET_NO_CLEANUP_REQUIRED = -1; + static final int WIDGET_PRELOAD_PENDING = 0; + static final int WIDGET_BOUND = 1; + static final int WIDGET_INFLATED = 2; + int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; + int mWidgetLoadingId = -1; + PendingAddWidgetInfo mCreateWidgetInfo = null; + private boolean mDraggingWidget = false; + + // Deferral of loading widget previews during launcher transitions + private boolean mInTransition; + private ArrayList mDeferredSyncWidgetPageItems = + new ArrayList(); + private ArrayList mDeferredPrepareLoadWidgetPreviewsTasks = + new ArrayList(); + + // Used for drawing shortcut previews + BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); + PaintCache mCachedShortcutPreviewPaint = new PaintCache(); + CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); + + // Used for drawing widget previews + CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); + RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); + RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); + PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); + + public AppsCustomizePagedView(Context context, AttributeSet attrs) { + super(context, attrs); + mLayoutInflater = LayoutInflater.from(context); + mPackageManager = context.getPackageManager(); + mApps = new ArrayList(); + mWidgets = new ArrayList(); + mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache(); + mCanvas = new Canvas(); + mRunningTasks = new ArrayList(); + + // Save the default widget preview background + Resources resources = context.getResources(); + mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo); + mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); + mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1); + mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1); + mWidgetWidthGap = + a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellWidthGap, 0); + mWidgetHeightGap = + a.getDimensionPixelSize(R.styleable.AppsCustomizePagedView_widgetCellHeightGap, 0); + mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2); + mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2); + mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0); + mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0); + a.recycle(); + mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); + + // The padding on the non-matched dimension for the default widget preview icons + // (top + bottom) + mFadeInAdjacentScreens = false; + + // Unless otherwise specified this view is important for accessibility. + if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + @Override + protected void init() { + super.init(); + mCenterPagesVertically = false; + + Context context = getContext(); + Resources r = context.getResources(); + setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f); + } + + @Override + protected void onUnhandledTap(MotionEvent ev) { + if (LauncherApplication.isScreenLarge()) { + // Dismiss AppsCustomize if we tap + mLauncher.showWorkspace(true); + } + } + + /** Returns the item index of the center item on this page so that we can restore to this + * item index when we rotate. */ + private int getMiddleComponentIndexOnCurrentPage() { + int i = -1; + if (getPageCount() > 0) { + int currentPage = getCurrentPage(); + if (currentPage < mNumAppsPages) { + PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(currentPage); + PagedViewCellLayoutChildren childrenLayout = layout.getChildrenLayout(); + int numItemsPerPage = mCellCountX * mCellCountY; + int childCount = childrenLayout.getChildCount(); + if (childCount > 0) { + i = (currentPage * numItemsPerPage) + (childCount / 2); + } + } else { + int numApps = mApps.size(); + PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage); + int numItemsPerPage = mWidgetCountX * mWidgetCountY; + int childCount = layout.getChildCount(); + if (childCount > 0) { + i = numApps + + ((currentPage - mNumAppsPages) * numItemsPerPage) + (childCount / 2); + } + } + } + return i; + } + + /** Get the index of the item to restore to if we need to restore the current page. */ + int getSaveInstanceStateIndex() { + if (mSaveInstanceStateItemIndex == -1) { + mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage(); + } + return mSaveInstanceStateItemIndex; + } + + /** Returns the page in the current orientation which is expected to contain the specified + * item index. */ + int getPageForComponent(int index) { + if (index < 0) return 0; + + if (index < mApps.size()) { + int numItemsPerPage = mCellCountX * mCellCountY; + return (index / numItemsPerPage); + } else { + int numItemsPerPage = mWidgetCountX * mWidgetCountY; + return mNumAppsPages + ((index - mApps.size()) / numItemsPerPage); + } + } + + /** Restores the page for an item at the specified index */ + void restorePageForIndex(int index) { + if (index < 0) return; + mSaveInstanceStateItemIndex = index; + } + + private void updatePageCounts() { + mNumWidgetPages = (int) Math.ceil(mWidgets.size() / + (float) (mWidgetCountX * mWidgetCountY)); + mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY)); + } + + protected void onDataReady(int width, int height) { + // Note that we transpose the counts in portrait so that we get a similar layout + boolean isLandscape = getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; + int maxCellCountX = Integer.MAX_VALUE; + int maxCellCountY = Integer.MAX_VALUE; + if (LauncherApplication.isScreenLarge()) { + maxCellCountX = (isLandscape ? LauncherModel.getCellCountX() : + LauncherModel.getCellCountY()); + maxCellCountY = (isLandscape ? LauncherModel.getCellCountY() : + LauncherModel.getCellCountX()); + } + if (mMaxAppCellCountX > -1) { + maxCellCountX = Math.min(maxCellCountX, mMaxAppCellCountX); + } + if (mMaxAppCellCountY > -1) { + maxCellCountY = Math.min(maxCellCountY, mMaxAppCellCountY); + } + + // Now that the data is ready, we can calculate the content width, the number of cells to + // use for each page + mWidgetSpacingLayout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); + mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, + mPageLayoutPaddingRight, mPageLayoutPaddingBottom); + mWidgetSpacingLayout.calculateCellCount(width, height, maxCellCountX, maxCellCountY); + mCellCountX = mWidgetSpacingLayout.getCellCountX(); + mCellCountY = mWidgetSpacingLayout.getCellCountY(); + updatePageCounts(); + + // Force a measure to update recalculate the gaps + int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); + int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); + mWidgetSpacingLayout.measure(widthSpec, heightSpec); + mContentWidth = mWidgetSpacingLayout.getContentWidth(); + + AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost(); + final boolean hostIsTransitioning = host.isTransitioning(); + + // Restore the page + int page = getPageForComponent(mSaveInstanceStateItemIndex); + invalidatePageData(Math.max(0, page), hostIsTransitioning); + + // Show All Apps cling if we are finished transitioning, otherwise, we will try again when + // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be + // returned while animating) + if (!hostIsTransitioning) { + post(new Runnable() { + @Override + public void run() { + showAllAppsCling(); + } + }); + } + } + + void showAllAppsCling() { + if (!mHasShownAllAppsCling && isDataReady()) { + mHasShownAllAppsCling = true; + // Calculate the position for the cling punch through + int[] offset = new int[2]; + int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY); + mLauncher.getDragLayer().getLocationInDragLayer(this, offset); + // PagedViews are centered horizontally but top aligned + pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + + offset[0]; + pos[1] += offset[1]; + mLauncher.showFirstRunAllAppsCling(pos); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + if (!isDataReady()) { + if (!mApps.isEmpty() && !mWidgets.isEmpty()) { + setDataIsReady(); + setMeasuredDimension(width, height); + onDataReady(width, height); + } + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public void onPackagesUpdated() { + // TODO: this isn't ideal, but we actually need to delay here. This call is triggered + // by a broadcast receiver, and in order for it to work correctly, we need to know that + // the AppWidgetService has already received and processed the same broadcast. Since there + // is no guarantee about ordering of broadcast receipt, we just delay here. This is a + // workaround until we add a callback from AppWidgetService to AppWidgetHost when widget + // packages are added, updated or removed. + postDelayed(new Runnable() { + public void run() { + updatePackages(); + } + }, 1500); + } + + public void updatePackages() { + // Get the list of widgets and shortcuts + mWidgets.clear(); + List widgets = + AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); + Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); + List shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0); + for (AppWidgetProviderInfo widget : widgets) { + if (widget.minWidth > 0 && widget.minHeight > 0) { + // Ensure that all widgets we show can be added on a workspace of this size + int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget); + int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget); + int minSpanX = Math.min(spanXY[0], minSpanXY[0]); + int minSpanY = Math.min(spanXY[1], minSpanXY[1]); + if (minSpanX <= LauncherModel.getCellCountX() && + minSpanY <= LauncherModel.getCellCountY()) { + mWidgets.add(widget); + } else { + Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" + + widget.minWidth + ", " + widget.minHeight + ")"); + } + } else { + Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" + + widget.minWidth + ", " + widget.minHeight + ")"); + } + } + mWidgets.addAll(shortcuts); + Collections.sort(mWidgets, + new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); + updatePageCounts(); + invalidateOnDataChange(); + } + + @Override + public void onClick(View v) { + // When we have exited all apps or are in transition, disregard clicks + if (!mLauncher.isAllAppsCustomizeOpen() || + mLauncher.getWorkspace().isSwitchingState()) return; + + if (v instanceof PagedViewIcon) { + // Animate some feedback to the click + final ApplicationInfo appInfo = (ApplicationInfo) v.getTag(); + + // Lock the drawable state to pressed until we return to Launcher + if (mPressedIcon != null) { + mPressedIcon.lockDrawableState(); + } + + // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled + // to be consistent. So re-enable the flag here, and we will re-disable it as necessary + // when Launcher resumes and we are still in AllApps. + mLauncher.updateWallpaperVisibility(true); + mLauncher.startActivitySafely(v, appInfo.intent, appInfo); + + } else if (v instanceof PagedViewWidget) { + // Let the user know that they have to long press to add a widget + Toast.makeText(getContext(), R.string.long_press_widget_to_add, + Toast.LENGTH_SHORT).show(); + + // Create a little animation to show that the widget can move + float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); + final ImageView p = (ImageView) v.findViewById(R.id.widget_preview); + AnimatorSet bounce = new AnimatorSet(); + ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY); + tyuAnim.setDuration(125); + ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f); + tydAnim.setDuration(100); + bounce.play(tyuAnim).before(tydAnim); + bounce.setInterpolator(new AccelerateInterpolator()); + bounce.start(); + } + } + + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event); + } + + /* + * PagedViewWithDraggableItems implementation + */ + @Override + protected void determineDraggingStart(android.view.MotionEvent ev) { + // Disable dragging by pulling an app down for now. + } + + private void beginDraggingApplication(View v) { + mLauncher.getWorkspace().onDragStartedWithItem(v); + mLauncher.getWorkspace().beginDragShared(v, this); + } + + private void preloadWidget(final PendingAddWidgetInfo info) { + final AppWidgetProviderInfo pInfo = info.info; + if (pInfo.configure != null) { + return; + } + + mWidgetCleanupState = WIDGET_PRELOAD_PENDING; + mBindWidgetRunnable = new Runnable() { + @Override + public void run() { + mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); + if (AppWidgetManager.getInstance(mLauncher) + .bindAppWidgetIdIfAllowed(mWidgetLoadingId, info.componentName)) { + mWidgetCleanupState = WIDGET_BOUND; + } + } + }; + post(mBindWidgetRunnable); + + mInflateWidgetRunnable = new Runnable() { + @Override + public void run() { + AppWidgetHostView hostView = mLauncher. + getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo); + info.boundWidget = hostView; + mWidgetCleanupState = WIDGET_INFLATED; + hostView.setVisibility(INVISIBLE); + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX, + info.spanY, info, false); + + // We want the first widget layout to be the correct size. This will be important + // for width size reporting to the AppWidgetManager. + DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], + unScaledSize[1]); + lp.x = lp.y = 0; + lp.customPosition = true; + hostView.setLayoutParams(lp); + mLauncher.getDragLayer().addView(hostView); + } + }; + post(mInflateWidgetRunnable); + } + + @Override + public void onShortPress(View v) { + // We are anticipating a long press, and we use this time to load bind and instantiate + // the widget. This will need to be cleaned up if it turns out no long press occurs. + if (mCreateWidgetInfo != null) { + // Just in case the cleanup process wasn't properly executed. This shouldn't happen. + cleanupWidgetPreloading(false); + } + mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); + preloadWidget(mCreateWidgetInfo); + } + + private void cleanupWidgetPreloading(boolean widgetWasAdded) { + if (!widgetWasAdded) { + // If the widget was not added, we may need to do further cleanup. + PendingAddWidgetInfo info = mCreateWidgetInfo; + mCreateWidgetInfo = null; + + if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) { + // We never did any preloading, so just remove pending callbacks to do so + removeCallbacks(mBindWidgetRunnable); + removeCallbacks(mInflateWidgetRunnable); + } else if (mWidgetCleanupState == WIDGET_BOUND) { + // Delete the widget id which was allocated + if (mWidgetLoadingId != -1) { + mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); + } + + // We never got around to inflating the widget, so remove the callback to do so. + removeCallbacks(mInflateWidgetRunnable); + } else if (mWidgetCleanupState == WIDGET_INFLATED) { + // Delete the widget id which was allocated + if (mWidgetLoadingId != -1) { + mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); + } + + // The widget was inflated and added to the DragLayer -- remove it. + AppWidgetHostView widget = info.boundWidget; + mLauncher.getDragLayer().removeView(widget); + } + } + mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED; + mWidgetLoadingId = -1; + mCreateWidgetInfo = null; + PagedViewWidget.resetShortPressTarget(); + } + + @Override + public void cleanUpShortPress(View v) { + if (!mDraggingWidget) { + cleanupWidgetPreloading(false); + } + } + + private boolean beginDraggingWidget(View v) { + mDraggingWidget = true; + // Get the widget preview as the drag representation + ImageView image = (ImageView) v.findViewById(R.id.widget_preview); + PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); + + // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and + // we abort the drag. + if (image.getDrawable() == null) { + mDraggingWidget = false; + return false; + } + + // Compose the drag image + Bitmap preview; + Bitmap outline; + float scale = 1f; + if (createItemInfo instanceof PendingAddWidgetInfo) { + // This can happen in some weird cases involving multi-touch. We can't start dragging + // the widget if this is null, so we break out. + if (mCreateWidgetInfo == null) { + return false; + } + + PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo; + createItemInfo = createWidgetInfo; + int spanX = createItemInfo.spanX; + int spanY = createItemInfo.spanY; + int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY, + createWidgetInfo, true); + + FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable(); + float minScale = 1.25f; + int maxWidth, maxHeight; + maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]); + maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]); + preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage, + createWidgetInfo.icon, spanX, spanY, maxWidth, maxHeight); + + // Determine the image view drawable scale relative to the preview + float[] mv = new float[9]; + Matrix m = new Matrix(); + m.setRectToRect( + new RectF(0f, 0f, (float) preview.getWidth(), (float) preview.getHeight()), + new RectF(0f, 0f, (float) previewDrawable.getIntrinsicWidth(), + (float) previewDrawable.getIntrinsicHeight()), + Matrix.ScaleToFit.START); + m.getValues(mv); + scale = (float) mv[0]; + } else { + PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag(); + Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo); + preview = Bitmap.createBitmap(icon.getIntrinsicWidth(), + icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + + mCanvas.setBitmap(preview); + mCanvas.save(); + renderDrawableToBitmap(icon, preview, 0, 0, + icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + mCanvas.restore(); + mCanvas.setBitmap(null); + createItemInfo.spanX = createItemInfo.spanY = 1; + } + + // We use a custom alpha clip table for the default widget previews + Paint alphaClipPaint = null; + if (createItemInfo instanceof PendingAddWidgetInfo) { + if (((PendingAddWidgetInfo) createItemInfo).previewImage != 0) { + MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(0, 255); + alphaClipPaint = new Paint(); + alphaClipPaint.setMaskFilter(alphaClipTable); + } + } + + // Save the preview for the outline generation, then dim the preview + outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(), + false); + + // Start the drag + alphaClipPaint = null; + mLauncher.lockScreenOrientation(); + mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, alphaClipPaint); + mDragController.startDrag(image, preview, this, createItemInfo, + DragController.DRAG_ACTION_COPY, null, scale); + outline.recycle(); + preview.recycle(); + return true; + } + + @Override + protected boolean beginDragging(final View v) { + if (!super.beginDragging(v)) return false; + + if (v instanceof PagedViewIcon) { + beginDraggingApplication(v); + } else if (v instanceof PagedViewWidget) { + if (!beginDraggingWidget(v)) { + return false; + } + } + + // We delay entering spring-loaded mode slightly to make sure the UI + // thready is free of any work. + postDelayed(new Runnable() { + @Override + public void run() { + // We don't enter spring-loaded mode if the drag has been cancelled + if (mLauncher.getDragController().isDragging()) { + // Dismiss the cling + mLauncher.dismissAllAppsCling(null); + + // Reset the alpha on the dragged icon before we drag + resetDrawableState(); + + // Go into spring loaded mode (must happen before we startDrag()) + mLauncher.enterSpringLoadedDragMode(); + } + } + }, 150); + + return true; + } + + /** + * Clean up after dragging. + * + * @param target where the item was dragged to (can be null if the item was flung) + */ + private void endDragging(View target, boolean isFlingToDelete, boolean success) { + if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && + !(target instanceof DeleteDropTarget))) { + // Exit spring loaded mode if we have not successfully dropped or have not handled the + // drop in Workspace + mLauncher.exitSpringLoadedDragMode(); + } + mLauncher.unlockScreenOrientation(false); + } + + @Override + public View getContent() { + return null; + } + + @Override + public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { + mInTransition = true; + if (toWorkspace) { + cancelAllTasks(); + } + } + + @Override + public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { + } + + @Override + public void onLauncherTransitionStep(Launcher l, float t) { + } + + @Override + public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { + mInTransition = false; + for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) { + onSyncWidgetPageItems(d); + } + mDeferredSyncWidgetPageItems.clear(); + for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) { + r.run(); + } + mDeferredPrepareLoadWidgetPreviewsTasks.clear(); + mForceDrawAllChildrenNextFrame = !toWorkspace; + } + + @Override + public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, + boolean success) { + // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling + if (isFlingToDelete) return; + + endDragging(target, false, success); + + // Display an error message if the drag failed due to there not being enough space on the + // target layout we were dropping on. + if (!success) { + boolean showOutOfSpaceMessage = false; + if (target instanceof Workspace) { + int currentScreen = mLauncher.getCurrentWorkspaceScreen(); + Workspace workspace = (Workspace) target; + CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); + ItemInfo itemInfo = (ItemInfo) d.dragInfo; + if (layout != null) { + layout.calculateSpans(itemInfo); + showOutOfSpaceMessage = + !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); + } + } + if (showOutOfSpaceMessage) { + mLauncher.showOutOfSpaceMessage(false); + } + + d.deferDragViewCleanupPostAnimation = false; + } + cleanupWidgetPreloading(success); + mDraggingWidget = false; + } + + @Override + public void onFlingToDeleteCompleted() { + // We just dismiss the drag when we fling, so cleanup here + endDragging(null, true, true); + cleanupWidgetPreloading(false); + mDraggingWidget = false; + } + + @Override + public boolean supportsFlingToDelete() { + return true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + cancelAllTasks(); + } + + public void clearAllWidgetPages() { + cancelAllTasks(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View v = getPageAt(i); + if (v instanceof PagedViewGridLayout) { + ((PagedViewGridLayout) v).removeAllViewsOnPage(); + mDirtyPageContent.set(i, true); + } + } + } + + private void cancelAllTasks() { + // Clean up all the async tasks + Iterator iter = mRunningTasks.iterator(); + while (iter.hasNext()) { + AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); + task.cancel(false); + iter.remove(); + mDirtyPageContent.set(task.page, true); + + // We've already preallocated the views for the data to load into, so clear them as well + View v = getPageAt(task.page); + if (v instanceof PagedViewGridLayout) { + ((PagedViewGridLayout) v).removeAllViewsOnPage(); + } + } + mDeferredSyncWidgetPageItems.clear(); + mDeferredPrepareLoadWidgetPreviewsTasks.clear(); + } + + public void setContentType(ContentType type) { + if (type == ContentType.Widgets) { + invalidatePageData(mNumAppsPages, true); + } else if (type == ContentType.Applications) { + invalidatePageData(0, true); + } + } + + protected void snapToPage(int whichPage, int delta, int duration) { + super.snapToPage(whichPage, delta, duration); + updateCurrentTab(whichPage); + + // Update the thread priorities given the direction lookahead + Iterator iter = mRunningTasks.iterator(); + while (iter.hasNext()) { + AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); + int pageIndex = task.page; + if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) || + (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) { + task.setThreadPriority(getThreadPriorityForPage(pageIndex)); + } else { + task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); + } + } + } + + private void updateCurrentTab(int currentPage) { + AppsCustomizeTabHost tabHost = getTabHost(); + if (tabHost != null) { + String tag = tabHost.getCurrentTabTag(); + if (tag != null) { + if (currentPage >= mNumAppsPages && + !tag.equals(tabHost.getTabTagForContentType(ContentType.Widgets))) { + tabHost.setCurrentTabFromContent(ContentType.Widgets); + } else if (currentPage < mNumAppsPages && + !tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { + tabHost.setCurrentTabFromContent(ContentType.Applications); + } + } + } + } + + /* + * Apps PagedView implementation + */ + private void setVisibilityOnChildren(ViewGroup layout, int visibility) { + int childCount = layout.getChildCount(); + for (int i = 0; i < childCount; ++i) { + layout.getChildAt(i).setVisibility(visibility); + } + } + private void setupPage(PagedViewCellLayout layout) { + layout.setCellCount(mCellCountX, mCellCountY); + layout.setGap(mPageLayoutWidthGap, mPageLayoutHeightGap); + layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, + mPageLayoutPaddingRight, mPageLayoutPaddingBottom); + + // Note: We force a measure here to get around the fact that when we do layout calculations + // immediately after syncing, we don't have a proper width. That said, we already know the + // expected page width, so we can actually optimize by hiding all the TextView-based + // children that are expensive to measure, and let that happen naturally later. + setVisibilityOnChildren(layout, View.GONE); + int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); + int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); + layout.setMinimumWidth(getPageContentWidth()); + layout.measure(widthSpec, heightSpec); + setVisibilityOnChildren(layout, View.VISIBLE); + } + + public void syncAppsPageItems(int page, boolean immediate) { + // 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) getPageAt(page); + + layout.removeAllViewsOnPage(); + ArrayList items = new ArrayList(); + ArrayList images = new ArrayList(); + for (int i = startIndex; i < endIndex; ++i) { + ApplicationInfo info = mApps.get(i); + PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( + R.layout.apps_customize_application, layout, false); + icon.applyFromApplicationInfo(info, true, this); + icon.setOnClickListener(this); + icon.setOnLongClickListener(this); + icon.setOnTouchListener(this); + icon.setOnKeyListener(this); + + int index = i - startIndex; + int x = index % mCellCountX; + int y = index / mCellCountX; + layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); + + items.add(info); + images.add(info.iconBitmap); + } + + layout.createHardwareLayers(); + } + + /** + * A helper to return the priority for loading of the specified widget page. + */ + private int getWidgetPageLoadPriority(int page) { + // If we are snapping to another page, use that index as the target page index + int toPage = mCurrentPage; + if (mNextPage > -1) { + toPage = mNextPage; + } + + // We use the distance from the target page as an initial guess of priority, but if there + // are no pages of higher priority than the page specified, then bump up the priority of + // the specified page. + Iterator iter = mRunningTasks.iterator(); + int minPageDiff = Integer.MAX_VALUE; + while (iter.hasNext()) { + AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); + minPageDiff = Math.abs(task.page - toPage); + } + + int rawPageDiff = Math.abs(page - toPage); + return rawPageDiff - Math.min(rawPageDiff, minPageDiff); + } + /** + * Return the appropriate thread priority for loading for a given page (we give the current + * page much higher priority) + */ + private int getThreadPriorityForPage(int page) { + // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below + int pageDiff = getWidgetPageLoadPriority(page); + if (pageDiff <= 0) { + return Process.THREAD_PRIORITY_LESS_FAVORABLE; + } else if (pageDiff <= 1) { + return Process.THREAD_PRIORITY_LOWEST; + } else { + return Process.THREAD_PRIORITY_LOWEST; + } + } + private int getSleepForPage(int page) { + int pageDiff = getWidgetPageLoadPriority(page); + return Math.max(0, pageDiff * sPageSleepDelay); + } + /** + * Creates and executes a new AsyncTask to load a page of widget previews. + */ + private void prepareLoadWidgetPreviewsTask(int page, ArrayList widgets, + int cellWidth, int cellHeight, int cellCountX) { + + // Prune all tasks that are no longer needed + Iterator iter = mRunningTasks.iterator(); + while (iter.hasNext()) { + AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); + int taskPage = task.page; + if (taskPage < getAssociatedLowerPageBound(mCurrentPage) || + taskPage > getAssociatedUpperPageBound(mCurrentPage)) { + task.cancel(false); + iter.remove(); + } else { + task.setThreadPriority(getThreadPriorityForPage(taskPage)); + } + } + + // We introduce a slight delay to order the loading of side pages so that we don't thrash + final int sleepMs = getSleepForPage(page); + AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight, + new AsyncTaskCallback() { + @Override + public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { + try { + try { + Thread.sleep(sleepMs); + } catch (Exception e) {} + loadWidgetPreviewsInBackground(task, data); + } finally { + if (task.isCancelled()) { + data.cleanup(true); + } + } + } + }, + new AsyncTaskCallback() { + @Override + public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) { + mRunningTasks.remove(task); + if (task.isCancelled()) return; + // do cleanup inside onSyncWidgetPageItems + onSyncWidgetPageItems(data); + } + }); + + // Ensure that the task is appropriately prioritized and runs in parallel + AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page, + AsyncTaskPageData.Type.LoadWidgetPreviewData); + t.setThreadPriority(getThreadPriorityForPage(page)); + t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData); + mRunningTasks.add(t); + } + + /* + * Widgets PagedView implementation + */ + private void setupPage(PagedViewGridLayout layout) { + layout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop, + mPageLayoutPaddingRight, mPageLayoutPaddingBottom); + + // Note: We force a measure here to get around the fact that when we do layout calculations + // immediately after syncing, we don't have a proper width. + int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST); + int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST); + layout.setMinimumWidth(getPageContentWidth()); + layout.measure(widthSpec, heightSpec); + } + + private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) { + renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); + } + + private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, + float scale) { + if (bitmap != null) { + Canvas c = new Canvas(bitmap); + c.scale(scale, scale); + Rect oldBounds = d.copyBounds(); + d.setBounds(x, y, x + w, y + h); + d.draw(c); + d.setBounds(oldBounds); // Restore the bounds + c.setBitmap(null); + } + } + + private Bitmap getShortcutPreview(ResolveInfo info, int maxWidth, int maxHeight) { + Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get(); + final Canvas c = mCachedShortcutPreviewCanvas.get(); + if (tempBitmap == null || + tempBitmap.getWidth() != maxWidth || + tempBitmap.getHeight() != maxHeight) { + tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); + mCachedShortcutPreviewBitmap.set(tempBitmap); + } else { + c.setBitmap(tempBitmap); + c.drawColor(0, PorterDuff.Mode.CLEAR); + c.setBitmap(null); + } + // Render the icon + Drawable icon = mIconCache.getFullResIcon(info); + + int paddingTop = + getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); + int paddingLeft = + getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left); + int paddingRight = + getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right); + + int scaledIconWidth = (maxWidth - paddingLeft - paddingRight); + float scaleSize = scaledIconWidth / (float) mAppIconSize; + + renderDrawableToBitmap( + icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth); + + Bitmap preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); + c.setBitmap(preview); + Paint p = mCachedShortcutPreviewPaint.get(); + if (p == null) { + p = new Paint(); + ColorMatrix colorMatrix = new ColorMatrix(); + colorMatrix.setSaturation(0); + p.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); + p.setAlpha((int) (255 * 0.06f)); + //float density = 1f; + //p.setMaskFilter(new BlurMaskFilter(15*density, BlurMaskFilter.Blur.NORMAL)); + mCachedShortcutPreviewPaint.set(p); + } + c.drawBitmap(tempBitmap, 0, 0, p); + c.setBitmap(null); + + renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize); + + return preview; + } + + private Bitmap getWidgetPreview(ComponentName provider, int previewImage, + int iconId, int cellHSpan, int cellVSpan, int maxWidth, + int maxHeight) { + // Load the preview image if possible + String packageName = provider.getPackageName(); + if (maxWidth < 0) maxWidth = Integer.MAX_VALUE; + if (maxHeight < 0) maxHeight = Integer.MAX_VALUE; + + Drawable drawable = null; + if (previewImage != 0) { + drawable = mPackageManager.getDrawable(packageName, previewImage, null); + if (drawable == null) { + Log.w(TAG, "Can't load widget preview drawable 0x" + + Integer.toHexString(previewImage) + " for provider: " + provider); + } + } + + int bitmapWidth; + int bitmapHeight; + Bitmap defaultPreview = null; + boolean widgetPreviewExists = (drawable != null); + if (widgetPreviewExists) { + bitmapWidth = drawable.getIntrinsicWidth(); + bitmapHeight = drawable.getIntrinsicHeight(); + } else { + // Generate a preview image if we couldn't load one + if (cellHSpan < 1) cellHSpan = 1; + if (cellVSpan < 1) cellVSpan = 1; + + BitmapDrawable previewDrawable = (BitmapDrawable) getResources() + .getDrawable(R.drawable.widget_preview_tile); + final int previewDrawableWidth = previewDrawable + .getIntrinsicWidth(); + final int previewDrawableHeight = previewDrawable + .getIntrinsicHeight(); + bitmapWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips + bitmapHeight = previewDrawableHeight * cellVSpan; + + defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, + Config.ARGB_8888); + final Canvas c = mCachedAppWidgetPreviewCanvas.get(); + c.setBitmap(defaultPreview); + previewDrawable.setBounds(0, 0, bitmapWidth, bitmapHeight); + previewDrawable.setTileModeXY(Shader.TileMode.REPEAT, + Shader.TileMode.REPEAT); + previewDrawable.draw(c); + c.setBitmap(null); + + // Draw the icon in the top left corner + int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); + int smallestSide = Math.min(bitmapWidth, bitmapHeight); + float iconScale = Math.min((float) smallestSide + / (mAppIconSize + 2 * minOffset), 1f); + + try { + Drawable icon = null; + int hoffset = + (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); + int yoffset = + (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); + if (iconId > 0) + icon = mIconCache.getFullResIcon(packageName, iconId); + Resources resources = mLauncher.getResources(); + if (icon != null) { + renderDrawableToBitmap(icon, defaultPreview, hoffset, + yoffset, (int) (mAppIconSize * iconScale), + (int) (mAppIconSize * iconScale)); + } + } catch (Resources.NotFoundException e) { + } + } + + // Scale to fit width only - let the widget preview be clipped in the + // vertical dimension + float scale = 1f; + if (bitmapWidth > maxWidth) { + scale = maxWidth / (float) bitmapWidth; + } + if (scale != 1f) { + bitmapWidth = (int) (scale * bitmapWidth); + bitmapHeight = (int) (scale * bitmapHeight); + } + + Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, + Config.ARGB_8888); + + // Draw the scaled preview into the final bitmap + if (widgetPreviewExists) { + renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, + bitmapHeight); + } else { + final Canvas c = mCachedAppWidgetPreviewCanvas.get(); + final Rect src = mCachedAppWidgetPreviewSrcRect.get(); + final Rect dest = mCachedAppWidgetPreviewDestRect.get(); + c.setBitmap(preview); + src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); + dest.set(0, 0, preview.getWidth(), preview.getHeight()); + + Paint p = mCachedAppWidgetPreviewPaint.get(); + if (p == null) { + p = new Paint(); + p.setFilterBitmap(true); + mCachedAppWidgetPreviewPaint.set(p); + } + c.drawBitmap(defaultPreview, src, dest, p); + c.setBitmap(null); + } + return preview; + } + + public void syncWidgetPageItems(final int page, final boolean immediate) { + int numItemsPerPage = mWidgetCountX * mWidgetCountY; + + // Calculate the dimensions of each cell we are giving to each widget + final ArrayList items = new ArrayList(); + int contentWidth = mWidgetSpacingLayout.getContentWidth(); + final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight + - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX); + int contentHeight = mWidgetSpacingLayout.getContentHeight(); + final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom + - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY); + + // Prepare the set of widgets to load previews for in the background + int offset = (page - mNumAppsPages) * numItemsPerPage; + for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) { + items.add(mWidgets.get(i)); + } + + // Prepopulate the pages with the other widget info, and fill in the previews later + final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); + layout.setColumnCount(layout.getCellCountX()); + for (int i = 0; i < items.size(); ++i) { + Object rawInfo = items.get(i); + PendingAddItemInfo createItemInfo = null; + PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate( + R.layout.apps_customize_widget, layout, false); + if (rawInfo instanceof AppWidgetProviderInfo) { + // Fill in the widget information + AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; + createItemInfo = new PendingAddWidgetInfo(info, null, null); + + // Determine the widget spans and min resize spans. + int[] spanXY = Launcher.getSpanForWidget(mLauncher, info); + createItemInfo.spanX = spanXY[0]; + createItemInfo.spanY = spanXY[1]; + int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info); + createItemInfo.minSpanX = minSpanXY[0]; + createItemInfo.minSpanY = minSpanXY[1]; + + widget.applyFromAppWidgetProviderInfo(info, -1, spanXY); + widget.setTag(createItemInfo); + widget.setShortPressListener(this); + } else if (rawInfo instanceof ResolveInfo) { + // Fill in the shortcuts information + ResolveInfo info = (ResolveInfo) rawInfo; + createItemInfo = new PendingAddShortcutInfo(info.activityInfo); + createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; + createItemInfo.componentName = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + widget.applyFromResolveInfo(mPackageManager, info); + widget.setTag(createItemInfo); + } + widget.setOnClickListener(this); + widget.setOnLongClickListener(this); + widget.setOnTouchListener(this); + widget.setOnKeyListener(this); + + // Layout each widget + int ix = i % mWidgetCountX; + int iy = i / mWidgetCountX; + GridLayout.LayoutParams lp = new GridLayout.LayoutParams( + GridLayout.spec(iy, GridLayout.LEFT), + GridLayout.spec(ix, GridLayout.TOP)); + lp.width = cellWidth; + lp.height = cellHeight; + lp.setGravity(Gravity.TOP | Gravity.LEFT); + if (ix > 0) lp.leftMargin = mWidgetWidthGap; + if (iy > 0) lp.topMargin = mWidgetHeightGap; + layout.addView(widget, lp); + } + + // wait until a call on onLayout to start loading, because + // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out + // TODO: can we do a measure/layout immediately? + layout.setOnLayoutListener(new Runnable() { + public void run() { + // Load the widget previews + int maxPreviewWidth = cellWidth; + int maxPreviewHeight = cellHeight; + if (layout.getChildCount() > 0) { + PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0); + int[] maxSize = w.getPreviewSize(); + maxPreviewWidth = maxSize[0]; + maxPreviewHeight = maxSize[1]; + } + if (immediate) { + AsyncTaskPageData data = new AsyncTaskPageData(page, items, + maxPreviewWidth, maxPreviewHeight, null, null); + loadWidgetPreviewsInBackground(null, data); + onSyncWidgetPageItems(data); + } else { + if (mInTransition) { + mDeferredPrepareLoadWidgetPreviewsTasks.add(this); + } else { + prepareLoadWidgetPreviewsTask(page, items, + maxPreviewWidth, maxPreviewHeight, mWidgetCountX); + } + } + } + }); + } + private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task, + AsyncTaskPageData data) { + // loadWidgetPreviewsInBackground can be called without a task to load a set of widget + // previews synchronously + if (task != null) { + // Ensure that this task starts running at the correct priority + task.syncThreadPriority(); + } + + // Load each of the widget/shortcut previews + ArrayList items = data.items; + ArrayList images = data.generatedImages; + int count = items.size(); + for (int i = 0; i < count; ++i) { + if (task != null) { + // Ensure we haven't been cancelled yet + if (task.isCancelled()) break; + // Before work on each item, ensure that this task is running at the correct + // priority + task.syncThreadPriority(); + } + + Object rawInfo = items.get(i); + if (rawInfo instanceof AppWidgetProviderInfo) { + AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; + int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info); + + int maxWidth = Math.min(data.maxImageWidth, + mWidgetSpacingLayout.estimateCellWidth(cellSpans[0])); + int maxHeight = Math.min(data.maxImageHeight, + mWidgetSpacingLayout.estimateCellHeight(cellSpans[1])); + Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon, + cellSpans[0], cellSpans[1], maxWidth, maxHeight); + images.add(b); + } else if (rawInfo instanceof ResolveInfo) { + // Fill in the shortcuts information + ResolveInfo info = (ResolveInfo) rawInfo; + images.add(getShortcutPreview(info, data.maxImageWidth, data.maxImageHeight)); + } + } + } + + private void onSyncWidgetPageItems(AsyncTaskPageData data) { + if (mInTransition) { + mDeferredSyncWidgetPageItems.add(data); + return; + } + try { + int page = data.page; + PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page); + + ArrayList items = data.items; + int count = items.size(); + for (int i = 0; i < count; ++i) { + PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i); + if (widget != null) { + Bitmap preview = data.generatedImages.get(i); + widget.applyPreview(new FastBitmapDrawable(preview), i); + } + } + + layout.createHardwareLayer(); + invalidate(); + + // Update all thread priorities + Iterator iter = mRunningTasks.iterator(); + while (iter.hasNext()) { + AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); + int pageIndex = task.page; + task.setThreadPriority(getThreadPriorityForPage(pageIndex)); + } + } finally { + data.cleanup(false); + } + } + + @Override + public void syncPages() { + removeAllViews(); + cancelAllTasks(); + + Context context = getContext(); + for (int j = 0; j < mNumWidgetPages; ++j) { + PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, + mWidgetCountY); + setupPage(layout); + addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + } + + for (int i = 0; i < mNumAppsPages; ++i) { + PagedViewCellLayout layout = new PagedViewCellLayout(context); + setupPage(layout); + addView(layout); + } + } + + @Override + public void syncPageItems(int page, boolean immediate) { + if (page < mNumAppsPages) { + syncAppsPageItems(page, immediate); + } else { + syncWidgetPageItems(page, immediate); + } + } + + // We want our pages to be z-ordered such that the further a page is to the left, the higher + // it is in the z-order. This is important to insure touch events are handled correctly. + View getPageAt(int index) { + return getChildAt(indexToPage(index)); + } + + @Override + protected int indexToPage(int index) { + return getChildCount() - index - 1; + } + + // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. + @Override + protected void screenScrolled(int screenCenter) { + super.screenScrolled(screenCenter); + + for (int i = 0; i < getChildCount(); i++) { + View v = getPageAt(i); + if (v != null) { + float scrollProgress = getScrollProgress(screenCenter, v, i); + + float interpolatedProgress = + mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0))); + float scale = (1 - interpolatedProgress) + + interpolatedProgress * TRANSITION_SCALE_FACTOR; + float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth(); + + float alpha; + + if (scrollProgress < 0) { + alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( + 1 - Math.abs(scrollProgress)) : 1.0f; + } else { + // On large screens we need to fade the page as it nears its leftmost position + alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress); + } + + v.setCameraDistance(mDensity * CAMERA_DISTANCE); + int pageWidth = v.getMeasuredWidth(); + int pageHeight = v.getMeasuredHeight(); + + if (PERFORM_OVERSCROLL_ROTATION) { + if (i == 0 && scrollProgress < 0) { + // Overscroll to the left + v.setPivotX(TRANSITION_PIVOT * pageWidth); + v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); + scale = 1.0f; + alpha = 1.0f; + // On the first page, we don't want the page to have any lateral motion + translationX = 0; + } else if (i == getChildCount() - 1 && scrollProgress > 0) { + // Overscroll to the right + v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); + v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); + scale = 1.0f; + alpha = 1.0f; + // On the last page, we don't want the page to have any lateral motion. + translationX = 0; + } else { + v.setPivotY(pageHeight / 2.0f); + v.setPivotX(pageWidth / 2.0f); + v.setRotationY(0f); + } + } + + v.setTranslationX(translationX); + v.setScaleX(scale); + v.setScaleY(scale); + v.setAlpha(alpha); + + // If the view has 0 alpha, we set it to be invisible so as to prevent + // it from accepting touches + if (alpha == 0) { + v.setVisibility(INVISIBLE); + } else if (v.getVisibility() != VISIBLE) { + v.setVisibility(VISIBLE); + } + } + } + } + + protected void overScroll(float amount) { + acceleratedOverScroll(amount); + } + + /** + * Used by the parent to get the content width to set the tab bar to + * @return + */ + public int getPageContentWidth() { + return mContentWidth; + } + + @Override + protected void onPageEndMoving() { + super.onPageEndMoving(); + mForceDrawAllChildrenNextFrame = true; + // We reset the save index when we change pages so that it will be recalculated on next + // rotation + mSaveInstanceStateItemIndex = -1; + } + + /* + * AllAppsView implementation + */ + @Override + public void setup(Launcher launcher, DragController dragController) { + mLauncher = launcher; + mDragController = dragController; + } + @Override + public void zoom(float zoom, boolean animate) { + // TODO-APPS_CUSTOMIZE: Call back to mLauncher.zoomed() + } + @Override + public boolean isVisible() { + return (getVisibility() == VISIBLE); + } + @Override + public boolean isAnimating() { + return false; + } + + /** + * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can + * appropriately determine when to invalidate the PagedView page data. In cases where the data + * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the + * next onMeasure() pass, which will trigger an invalidatePageData() itself. + */ + private void invalidateOnDataChange() { + if (!isDataReady()) { + // The next layout pass will trigger data-ready if both widgets and apps are set, so + // request a layout to trigger the page data when ready. + requestLayout(); + } else { + cancelAllTasks(); + invalidatePageData(); + } + } + + @Override + public void setApps(ArrayList list) { + mApps = list; + Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR); + updatePageCounts(); + invalidateOnDataChange(); + } + private void addAppsWithoutInvalidate(ArrayList list) { + // We add it in place, in alphabetical order + int count = list.size(); + for (int i = 0; i < count; ++i) { + ApplicationInfo info = list.get(i); + int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR); + if (index < 0) { + mApps.add(-(index + 1), info); + } + } + } + @Override + public void addApps(ArrayList list) { + addAppsWithoutInvalidate(list); + updatePageCounts(); + invalidateOnDataChange(); + } + private int findAppByComponent(List list, ApplicationInfo item) { + ComponentName removeComponent = item.intent.getComponent(); + 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; + } + private void removeAppsWithoutInvalidate(ArrayList list) { + // loop through all the apps and remove apps that have the same component + int length = list.size(); + for (int i = 0; i < length; ++i) { + ApplicationInfo info = list.get(i); + int removeIndex = findAppByComponent(mApps, info); + if (removeIndex > -1) { + mApps.remove(removeIndex); + } + } + } + @Override + public void removeApps(ArrayList list) { + removeAppsWithoutInvalidate(list); + updatePageCounts(); + invalidateOnDataChange(); + } + @Override + public void updateApps(ArrayList 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); + updatePageCounts(); + invalidateOnDataChange(); + } + + @Override + public void reset() { + // If we have reset, then we should not continue to restore the previous state + mSaveInstanceStateItemIndex = -1; + + AppsCustomizeTabHost tabHost = getTabHost(); + String tag = tabHost.getCurrentTabTag(); + if (tag != null) { + if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) { + tabHost.setCurrentTabFromContent(ContentType.Applications); + } + } + + if (mCurrentPage != 0) { + invalidatePageData(0); + } + } + + private AppsCustomizeTabHost getTabHost() { + return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane); + } + + @Override + public void dumpState() { + // TODO: Dump information related to current list of Applications, Widgets, etc. + ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps); + dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets); + } + + private void dumpAppWidgetProviderInfoList(String tag, String label, + ArrayList list) { + Log.d(tag, label + " size=" + list.size()); + for (Object i: list) { + if (i instanceof AppWidgetProviderInfo) { + AppWidgetProviderInfo info = (AppWidgetProviderInfo) i; + Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage + + " resizeMode=" + info.resizeMode + " configure=" + info.configure + + " initialLayout=" + info.initialLayout + + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight); + } else if (i instanceof ResolveInfo) { + ResolveInfo info = (ResolveInfo) i; + Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon=" + + info.icon); + } + } + } + + @Override + public void surrender() { + // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we + // should stop this now. + + // Stop all background tasks + cancelAllTasks(); + } + + @Override + public void iconPressed(PagedViewIcon icon) { + // Reset the previously pressed icon and store a reference to the pressed icon so that + // we can reset it on return to Launcher (in Launcher.onResume()) + if (mPressedIcon != null) { + mPressedIcon.resetDrawableState(); + } + mPressedIcon = icon; + } + + public void resetDrawableState() { + if (mPressedIcon != null) { + mPressedIcon.resetDrawableState(); + mPressedIcon = null; + } + } + + /* + * We load an extra page on each side to prevent flashes from scrolling and loading of the + * widget previews in the background with the AsyncTasks. + */ + final static int sLookBehindPageCount = 2; + final static int sLookAheadPageCount = 2; + protected int getAssociatedLowerPageBound(int page) { + final int count = getChildCount(); + int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); + int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0); + return windowMinIndex; + } + protected int getAssociatedUpperPageBound(int page) { + final int count = getChildCount(); + int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1); + int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1), + count - 1); + return windowMaxIndex; + } + + @Override + protected String getCurrentPageDescription() { + int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + int stringId = R.string.default_scroll_format; + int count = 0; + + if (page < mNumAppsPages) { + stringId = R.string.apps_customize_apps_scroll_format; + count = mNumAppsPages; + } else { + page -= mNumAppsPages; + stringId = R.string.apps_customize_widgets_scroll_format; + count = mNumWidgetPages; + } + + return String.format(getContext().getString(stringId), page + 1, count); + } +} diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java b/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java new file mode 100644 index 000000000..917f62ed0 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; + +import java.util.ArrayList; + +public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable, + TabHost.OnTabChangeListener { + static final String LOG_TAG = "AppsCustomizeTabHost"; + + private static final String APPS_TAB_TAG = "APPS"; + private static final String WIDGETS_TAB_TAG = "WIDGETS"; + + private final LayoutInflater mLayoutInflater; + private ViewGroup mTabs; + private ViewGroup mTabsContainer; + private AppsCustomizePagedView mAppsCustomizePane; + private boolean mSuppressContentCallback = false; + private FrameLayout mAnimationBuffer; + private LinearLayout mContent; + + private boolean mInTransition; + private boolean mTransitioningToWorkspace; + private boolean mResetAfterTransition; + private Runnable mRelayoutAndMakeVisible; + + private Launcher mLauncher; + + public AppsCustomizeTabHost(Context context, AttributeSet attrs) { + super(context, attrs); + mLayoutInflater = LayoutInflater.from(context); + mRelayoutAndMakeVisible = new Runnable() { + public void run() { + mTabs.requestLayout(); + mTabsContainer.setAlpha(1f); + } + }; + } + + public void setup(Launcher launcher) { + mLauncher = launcher; + } + + /** + * Convenience methods to select specific tabs. We want to set the content type immediately + * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view + * reflects the new content (but doesn't do the animation and logic associated with changing + * tabs manually). + */ + private void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { + onTabChangedStart(); + onTabChangedEnd(type); + } + void selectAppsTab() { + setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); + setCurrentTabByTag(APPS_TAB_TAG); + } + void selectWidgetsTab() { + setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); + setCurrentTabByTag(WIDGETS_TAB_TAG); + } + + /** + * Setup the tab host and create all necessary tabs. + */ + @Override + protected void onFinishInflate() { + // Setup the tab host + setup(); + + final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); + final TabWidget tabs = getTabWidget(); + final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) + findViewById(R.id.apps_customize_pane_content); + mTabs = tabs; + mTabsContainer = tabsContainer; + mAppsCustomizePane = appsCustomizePane; + mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer); + mContent = (LinearLayout) findViewById(R.id.apps_customize_content); + if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); + + // Configure the tabs content factory to return the same paged view (that we change the + // content filter on) + TabContentFactory contentFactory = new TabContentFactory() { + public View createTabContent(String tag) { + return appsCustomizePane; + } + }; + + // Create the tabs + TextView tabView; + String label; + label = getContext().getString(R.string.all_apps_button_label); + tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); + tabView.setText(label); + tabView.setContentDescription(label); + addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); + label = getContext().getString(R.string.widgets_tab_label); + tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); + tabView.setText(label); + tabView.setContentDescription(label); + addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); + setOnTabChangedListener(this); + + // Setup the key listener to jump between the last tab view and the market icon + AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); + View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); + lastTab.setOnKeyListener(keyListener); + View shopButton = findViewById(R.id.market_button); + shopButton.setOnKeyListener(keyListener); + + // Hide the tab bar until we measure + mTabsContainer.setAlpha(0f); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Set the width of the tab list to the content width + if (remeasureTabWidth) { + int contentWidth = mAppsCustomizePane.getPageContentWidth(); + if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { + // Set the width and show the tab bar + mTabs.getLayoutParams().width = contentWidth; + post(mRelayoutAndMakeVisible); + } + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + // If we are mid transitioning to the workspace, then intercept touch events here so we + // can ignore them, otherwise we just let all apps handle the touch events. + if (mInTransition && mTransitioningToWorkspace) { + return true; + } + return super.onInterceptTouchEvent(ev); + }; + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Allow touch events to fall through to the workspace if we are transitioning there + if (mInTransition && mTransitioningToWorkspace) { + return super.onTouchEvent(event); + } + + // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall + // through to the workspace and trigger showWorkspace() + if (event.getY() < mAppsCustomizePane.getBottom()) { + return true; + } + return super.onTouchEvent(event); + } + + private void onTabChangedStart() { + mAppsCustomizePane.hideScrollingIndicator(false); + } + + private void reloadCurrentPage() { + if (!LauncherApplication.isScreenLarge()) { + mAppsCustomizePane.flashScrollingIndicator(true); + } + mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); + mAppsCustomizePane.requestFocus(); + } + + private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { + mAppsCustomizePane.setContentType(type); + } + + @Override + public void onTabChanged(String tabId) { + final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); + if (mSuppressContentCallback) { + mSuppressContentCallback = false; + return; + } + + // Animate the changing of the tab content by fading pages in and out + final Resources res = getResources(); + final int duration = res.getInteger(R.integer.config_tabTransitionDuration); + + // We post a runnable here because there is a delay while the first page is loading and + // the feedback from having changed the tab almost feels better than having it stick + post(new Runnable() { + @Override + public void run() { + if (mAppsCustomizePane.getMeasuredWidth() <= 0 || + mAppsCustomizePane.getMeasuredHeight() <= 0) { + reloadCurrentPage(); + return; + } + + // Take the visible pages and re-parent them temporarily to mAnimatorBuffer + // and then cross fade to the new pages + int[] visiblePageRange = new int[2]; + mAppsCustomizePane.getVisiblePages(visiblePageRange); + if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { + // If we can't get the visible page ranges, then just skip the animation + reloadCurrentPage(); + return; + } + ArrayList visiblePages = new ArrayList(); + for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { + visiblePages.add(mAppsCustomizePane.getPageAt(i)); + } + + // We want the pages to be rendered in exactly the same way as they were when + // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer + // to be exactly the same as mAppsCustomizePane, and below, set the left/top + // parameters to be correct for each of the pages + mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); + + // mAppsCustomizePane renders its children in reverse order, so + // add the pages to mAnimationBuffer in reverse order to match that behavior + for (int i = visiblePages.size() - 1; i >= 0; i--) { + View child = visiblePages.get(i); + if (child instanceof PagedViewCellLayout) { + ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); + } else if (child instanceof PagedViewGridLayout) { + ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); + } + PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); + mAppsCustomizePane.removeView(child); + PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); + mAnimationBuffer.setAlpha(1f); + mAnimationBuffer.setVisibility(View.VISIBLE); + LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), + child.getMeasuredHeight()); + p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0); + mAnimationBuffer.addView(child, p); + } + + // Toggle the new content + onTabChangedStart(); + onTabChangedEnd(type); + + // Animate the transition + ObjectAnimator outAnim = ObjectAnimator.ofFloat(mAnimationBuffer, "alpha", 0f); + outAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimationBuffer.setVisibility(View.GONE); + mAnimationBuffer.removeAllViews(); + } + @Override + public void onAnimationCancel(Animator animation) { + mAnimationBuffer.setVisibility(View.GONE); + mAnimationBuffer.removeAllViews(); + } + }); + ObjectAnimator inAnim = ObjectAnimator.ofFloat(mAppsCustomizePane, "alpha", 1f); + inAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + reloadCurrentPage(); + } + }); + AnimatorSet animSet = new AnimatorSet(); + animSet.playTogether(outAnim, inAnim); + animSet.setDuration(duration); + animSet.start(); + } + }); + } + + public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { + mSuppressContentCallback = true; + setCurrentTabByTag(getTabTagForContentType(type)); + } + + /** + * Returns the content type for the specified tab tag. + */ + public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { + if (tag.equals(APPS_TAB_TAG)) { + return AppsCustomizePagedView.ContentType.Applications; + } else if (tag.equals(WIDGETS_TAB_TAG)) { + return AppsCustomizePagedView.ContentType.Widgets; + } + return AppsCustomizePagedView.ContentType.Applications; + } + + /** + * Returns the tab tag for a given content type. + */ + public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { + if (type == AppsCustomizePagedView.ContentType.Applications) { + return APPS_TAB_TAG; + } else if (type == AppsCustomizePagedView.ContentType.Widgets) { + return WIDGETS_TAB_TAG; + } + return APPS_TAB_TAG; + } + + /** + * Disable focus on anything under this view in the hierarchy if we are not visible. + */ + @Override + public int getDescendantFocusability() { + if (getVisibility() != View.VISIBLE) { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } + return super.getDescendantFocusability(); + } + + void reset() { + if (mInTransition) { + // Defer to after the transition to reset + mResetAfterTransition = true; + } else { + // Reset immediately + mAppsCustomizePane.reset(); + } + } + + private void enableAndBuildHardwareLayer() { + // isHardwareAccelerated() checks if we're attached to a window and if that + // window is HW accelerated-- we were sometimes not attached to a window + // and buildLayer was throwing an IllegalStateException + if (isHardwareAccelerated()) { + // Turn on hardware layers for performance + setLayerType(LAYER_TYPE_HARDWARE, null); + + // force building the layer, so you don't get a blip early in an animation + // when the layer is created layer + buildLayer(); + + // Let the GC system know that now is a good time to do any garbage + // collection; makes it less likely we'll get a GC during the all apps + // to workspace animation + System.gc(); + } + } + + @Override + public View getContent() { + return mContent; + } + + /* LauncherTransitionable overrides */ + @Override + public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { + mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace); + mInTransition = true; + mTransitioningToWorkspace = toWorkspace; + + if (toWorkspace) { + // Going from All Apps -> Workspace + setVisibilityOfSiblingsWithLowerZOrder(VISIBLE); + // Stop the scrolling indicator - we don't want All Apps to be invalidating itself + // during the transition, especially since it has a hardware layer set on it + mAppsCustomizePane.cancelScrollingIndicatorAnimations(); + } else { + // Going from Workspace -> All Apps + mContent.setVisibility(VISIBLE); + + // Make sure the current page is loaded (we start loading the side pages after the + // transition to prevent slowing down the animation) + mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); + + if (!LauncherApplication.isScreenLarge()) { + mAppsCustomizePane.showScrollingIndicator(true); + } + } + + if (mResetAfterTransition) { + mAppsCustomizePane.reset(); + mResetAfterTransition = false; + } + } + + @Override + public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { + if (animated) { + enableAndBuildHardwareLayer(); + } + } + + @Override + public void onLauncherTransitionStep(Launcher l, float t) { + // Do nothing + } + + @Override + public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { + mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace); + mInTransition = false; + if (animated) { + setLayerType(LAYER_TYPE_NONE, null); + } + + if (!toWorkspace) { + // Going from Workspace -> All Apps + setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); + + // Dismiss the workspace cling and show the all apps cling (if not already shown) + l.dismissWorkspaceCling(null); + mAppsCustomizePane.showAllAppsCling(); + // Make sure adjacent pages are loaded (we wait until after the transition to + // prevent slowing down the animation) + mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); + + if (!LauncherApplication.isScreenLarge()) { + mAppsCustomizePane.hideScrollingIndicator(false); + } + } + } + + private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) { + ViewGroup parent = (ViewGroup) getParent(); + if (parent == null) return; + + final int count = parent.getChildCount(); + if (!isChildrenDrawingOrderEnabled()) { + for (int i = 0; i < count; i++) { + final View child = parent.getChildAt(i); + if (child == this) { + break; + } else { + if (child.getVisibility() == GONE) { + continue; + } + child.setVisibility(visibility); + } + } + } else { + throw new RuntimeException("Failed; can't get z-order of views"); + } + } + + public void onWindowVisible() { + if (getVisibility() == VISIBLE) { + mContent.setVisibility(VISIBLE); + // We unload the widget previews when the UI is hidden, so need to reload pages + // Load the current page synchronously, and the neighboring pages asynchronously + mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); + mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); + } + } + + public void onTrimMemory() { + mContent.setVisibility(GONE); + // Clear the widget pages of all their subviews - this will trigger the widget previews + // to delete their bitmaps + mAppsCustomizePane.clearAllWidgetPages(); + } + + boolean isTransitioning() { + return mInTransition; + } +} diff --git a/src/com/cyanogenmod/trebuchet/BubbleTextView.java b/src/com/cyanogenmod/trebuchet/BubbleTextView.java new file mode 100644 index 000000000..7b33a5e1f --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/BubbleTextView.java @@ -0,0 +1,334 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Region.Op; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.TextView; + +/** + * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan + * because we want to make the bubble taller than the text and TextView's clip is + * too aggressive. + */ +public class BubbleTextView extends TextView { + static final float CORNER_RADIUS = 4.0f; + static final float SHADOW_LARGE_RADIUS = 4.0f; + static final float SHADOW_SMALL_RADIUS = 1.75f; + static final float SHADOW_Y_OFFSET = 2.0f; + static final int SHADOW_LARGE_COLOUR = 0xDD000000; + static final int SHADOW_SMALL_COLOUR = 0xCC000000; + static final float PADDING_H = 8.0f; + static final float PADDING_V = 3.0f; + + private int mPrevAlpha = -1; + + private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper(); + private final Canvas mTempCanvas = new Canvas(); + private final Rect mTempRect = new Rect(); + private boolean mDidInvalidateForPressedState; + private Bitmap mPressedOrFocusedBackground; + private int mFocusedOutlineColor; + private int mFocusedGlowColor; + private int mPressedOutlineColor; + private int mPressedGlowColor; + + private boolean mBackgroundSizeChanged; + private Drawable mBackground; + + private boolean mStayPressed; + private CheckLongPressHelper mLongPressHelper; + + public BubbleTextView(Context context) { + super(context); + init(); + } + + public BubbleTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mLongPressHelper = new CheckLongPressHelper(this); + mBackground = getBackground(); + + final Resources res = getContext().getResources(); + mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor = + res.getColor(android.R.color.holo_blue_light); + + setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR); + } + + public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) { + Bitmap b = info.getIcon(iconCache); + + setCompoundDrawablesWithIntrinsicBounds(null, + new FastBitmapDrawable(b), + null, null); + setText(info.title); + setTag(info); + } + + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) { + mBackgroundSizeChanged = true; + } + return super.setFrame(left, top, right, bottom); + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return who == mBackground || super.verifyDrawable(who); + } + + @Override + protected void drawableStateChanged() { + if (isPressed()) { + // In this case, we have already created the pressed outline on ACTION_DOWN, + // so we just need to do an invalidate to trigger draw + if (!mDidInvalidateForPressedState) { + setCellLayoutPressedOrFocusedIcon(); + } + } else { + // Otherwise, either clear the pressed/focused background, or create a background + // for the focused state + final boolean backgroundEmptyBefore = mPressedOrFocusedBackground == null; + if (!mStayPressed) { + mPressedOrFocusedBackground = null; + } + if (isFocused()) { + if (getLayout() == null) { + // In some cases, we get focus before we have been layed out. Set the + // background to null so that it will get created when the view is drawn. + mPressedOrFocusedBackground = null; + } else { + mPressedOrFocusedBackground = createGlowingOutline( + mTempCanvas, mFocusedGlowColor, mFocusedOutlineColor); + } + mStayPressed = false; + setCellLayoutPressedOrFocusedIcon(); + } + final boolean backgroundEmptyNow = mPressedOrFocusedBackground == null; + if (!backgroundEmptyBefore && backgroundEmptyNow) { + setCellLayoutPressedOrFocusedIcon(); + } + } + + Drawable d = mBackground; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + super.drawableStateChanged(); + } + + /** + * Draw this BubbleTextView into the given Canvas. + * + * @param destCanvas the canvas to draw on + * @param padding the horizontal and vertical padding to use when drawing + */ + private void drawWithPadding(Canvas destCanvas, int padding) { + final Rect clipRect = mTempRect; + getDrawingRect(clipRect); + + // adjust the clip rect so that we don't include the text label + clipRect.bottom = + getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + getLayout().getLineTop(0); + + // 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.scale(getScaleX(), getScaleY(), + (getWidth() + padding) / 2, (getHeight() + padding) / 2); + destCanvas.translate(-getScrollX() + padding / 2, -getScrollY() + padding / 2); + destCanvas.clipRect(clipRect, Op.REPLACE); + 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 createGlowingOutline(Canvas canvas, int outlineColor, int glowColor) { + final int padding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; + final Bitmap b = Bitmap.createBitmap( + getWidth() + padding, getHeight() + padding, Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + drawWithPadding(canvas, padding); + mOutlineHelper.applyExtraThickExpensiveOutlineWithBlur(b, canvas, glowColor, outlineColor); + canvas.setBitmap(null); + + return b; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Call the superclass onTouchEvent first, because sometimes it changes the state to + // isPressed() on an ACTION_UP + boolean result = super.onTouchEvent(event); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // So that the pressed outline is visible immediately when isPressed() is true, + // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time + // to create it) + if (mPressedOrFocusedBackground == null) { + mPressedOrFocusedBackground = createGlowingOutline( + mTempCanvas, mPressedGlowColor, mPressedOutlineColor); + } + // Invalidate so the pressed state is visible, or set a flag so we know that we + // have to call invalidate as soon as the state is "pressed" + if (isPressed()) { + mDidInvalidateForPressedState = true; + setCellLayoutPressedOrFocusedIcon(); + } else { + mDidInvalidateForPressedState = false; + } + + mLongPressHelper.postCheckForLongPress(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // If we've touched down and up on an item, and it's still not "pressed", then + // destroy the pressed outline + if (!isPressed()) { + mPressedOrFocusedBackground = null; + } + + mLongPressHelper.cancelLongPress(); + break; + } + return result; + } + + void setStayPressed(boolean stayPressed) { + mStayPressed = stayPressed; + if (!stayPressed) { + mPressedOrFocusedBackground = null; + } + setCellLayoutPressedOrFocusedIcon(); + } + + void setCellLayoutPressedOrFocusedIcon() { + if (getParent() instanceof ShortcutAndWidgetContainer) { + ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent(); + if (parent != null) { + CellLayout layout = (CellLayout) parent.getParent(); + layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null); + } + } + } + + void clearPressedOrFocusedBackground() { + mPressedOrFocusedBackground = null; + setCellLayoutPressedOrFocusedIcon(); + } + + Bitmap getPressedOrFocusedBackground() { + return mPressedOrFocusedBackground; + } + + int getPressedOrFocusedBackgroundPadding() { + return HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2; + } + + @Override + public void draw(Canvas canvas) { + final Drawable background = mBackground; + if (background != null) { + final int scrollX = getScrollX(); + final int scrollY = getScrollY(); + + if (mBackgroundSizeChanged) { + background.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()); + mBackgroundSizeChanged = false; + } + + if ((scrollX | scrollY) == 0) { + background.draw(canvas); + } else { + canvas.translate(scrollX, scrollY); + background.draw(canvas); + canvas.translate(-scrollX, -scrollY); + } + } + + // If text is transparent, don't draw any shadow + if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) { + getPaint().clearShadowLayer(); + super.draw(canvas); + return; + } + + // We enhance the shadow by drawing the shadow twice + getPaint().setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR); + super.draw(canvas); + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), + getScrollX() + getWidth(), + getScrollY() + getHeight(), Region.Op.INTERSECT); + getPaint().setShadowLayer(SHADOW_SMALL_RADIUS, 0.0f, 0.0f, SHADOW_SMALL_COLOUR); + super.draw(canvas); + canvas.restore(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mBackground != null) mBackground.setCallback(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mBackground != null) mBackground.setCallback(null); + } + + @Override + protected boolean onSetAlpha(int alpha) { + if (mPrevAlpha != alpha) { + mPrevAlpha = alpha; + super.onSetAlpha(alpha); + } + return true; + } + + @Override + public void cancelLongPress() { + super.cancelLongPress(); + + mLongPressHelper.cancelLongPress(); + } +} diff --git a/src/com/cyanogenmod/trebuchet/ButtonDropTarget.java b/src/com/cyanogenmod/trebuchet/ButtonDropTarget.java new file mode 100644 index 000000000..8f819ea2f --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/ButtonDropTarget.java @@ -0,0 +1,147 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; + + +/** + * Implements a DropTarget. + */ +public class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener { + + protected final int mTransitionDuration; + + protected Launcher mLauncher; + private int mBottomDragPadding; + protected TextView mText; + protected SearchDropTargetBar mSearchDropTargetBar; + + /** Whether this drop target is active for the current drag */ + protected boolean mActive; + + /** The paint applied to the drag view on hover */ + protected int mHoverColor = 0; + + public ButtonDropTarget(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + Resources r = getResources(); + mTransitionDuration = r.getInteger(R.integer.config_dropTargetBgTransitionDuration); + mBottomDragPadding = r.getDimensionPixelSize(R.dimen.drop_target_drag_padding); + } + + void setLauncher(Launcher launcher) { + mLauncher = launcher; + } + + public boolean acceptDrop(DragObject d) { + return false; + } + + public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) { + mSearchDropTargetBar = searchDropTargetBar; + } + + protected Drawable getCurrentDrawable() { + Drawable[] drawables = getCompoundDrawables(); + for (int i = 0; i < drawables.length; ++i) { + if (drawables[i] != null) { + return drawables[i]; + } + } + return null; + } + + public void onDrop(DragObject d) { + } + + public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { + // Do nothing + } + + public void onDragEnter(DragObject d) { + d.dragView.setColor(mHoverColor); + } + + public void onDragOver(DragObject d) { + // Do nothing + } + + public void onDragExit(DragObject d) { + d.dragView.setColor(0); + } + + public void onDragStart(DragSource source, Object info, int dragAction) { + // Do nothing + } + + public boolean isDropEnabled() { + return mActive; + } + + public void onDragEnd() { + // Do nothing + } + + @Override + public void getHitRect(android.graphics.Rect outRect) { + super.getHitRect(outRect); + outRect.bottom += mBottomDragPadding; + } + + Rect getIconRect(int itemWidth, int itemHeight, int drawableWidth, int drawableHeight) { + DragLayer dragLayer = mLauncher.getDragLayer(); + + // Find the rect to animate to (the view is center aligned) + Rect to = new Rect(); + dragLayer.getViewRectRelativeToSelf(this, to); + int width = drawableWidth; + int height = drawableHeight; + int left = to.left + getPaddingLeft(); + int top = to.top + (getMeasuredHeight() - height) / 2; + to.set(left, top, left + width, top + height); + + // Center the destination rect about the trash icon + int xOffset = (int) -(itemWidth - width) / 2; + int yOffset = (int) -(itemHeight - height) / 2; + to.offset(xOffset, yOffset); + + return to; + } + + @Override + public DropTarget getDropTargetDelegate(DragObject d) { + return null; + } + + public void getLocationInDragLayer(int[] loc) { + mLauncher.getDragLayer().getLocationInDragLayer(this, loc); + } +} diff --git a/src/com/cyanogenmod/trebuchet/CellLayout.java b/src/com/cyanogenmod/trebuchet/CellLayout.java new file mode 100644 index 000000000..39a51d9b7 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/CellLayout.java @@ -0,0 +1,3022 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LayoutAnimationController; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.FolderIcon.FolderRingAnimator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Stack; + +public class CellLayout extends ViewGroup { + static final String TAG = "CellLayout"; + + private Launcher mLauncher; + private int mCellWidth; + private int mCellHeight; + + private int mCountX; + private int mCountY; + + private int mOriginalWidthGap; + private int mOriginalHeightGap; + private int mWidthGap; + private int mHeightGap; + private int mMaxGap; + private boolean mScrollingTransformsDirty = false; + + private final Rect mRect = new Rect(); + private final CellInfo mCellInfo = new CellInfo(); + + // 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[] mTmpXY = new int[2]; + private final int[] mTmpPoint = new int[2]; + int[] mTempLocation = new int[2]; + + boolean[][] mOccupied; + boolean[][] mTmpOccupied; + private boolean mLastDownOnOccupiedCell = false; + + private OnTouchListener mInterceptTouchListener; + + private ArrayList mFolderOuterRings = new ArrayList(); + private int[] mFolderLeaveBehindCell = {-1, -1}; + + private int mForegroundAlpha = 0; + private float mBackgroundAlpha; + private float mBackgroundAlphaMultiplier = 1.0f; + + private Drawable mNormalBackground; + private Drawable mActiveGlowBackground; + private Drawable mOverScrollForegroundDrawable; + private Drawable mOverScrollLeft; + private Drawable mOverScrollRight; + private Rect mBackgroundRect; + private Rect mForegroundRect; + private int mForegroundPadding; + + // If we're actively dragging something over this screen, mIsDragOverlapping is true + private boolean mIsDragOverlapping = false; + private final Point mDragCenter = new Point(); + + // These arrays are used to implement the drag visualization on x-large screens. + // They are used as circular arrays, indexed by mDragOutlineCurrent. + private Rect[] mDragOutlines = new Rect[4]; + 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 BubbleTextView mPressedOrFocusedIcon; + + private HashMap mReorderAnimators = new + HashMap(); + private HashMap + mShakeAnimators = new HashMap(); + + private boolean mItemPlacementDirty = false; + + // When a drag operation is in progress, holds the nearest cell to the touch point + private final int[] mDragCell = new int[2]; + + private boolean mDragging = false; + + private TimeInterpolator mEaseOutInterpolator; + private ShortcutAndWidgetContainer mShortcutsAndWidgets; + + private boolean mIsHotseat = false; + + public static final int MODE_DRAG_OVER = 0; + public static final int MODE_ON_DROP = 1; + public static final int MODE_ON_DROP_EXTERNAL = 2; + public static final int MODE_ACCEPT_DROP = 3; + private static final boolean DESTRUCTIVE_REORDER = false; + private static final boolean DEBUG_VISUALIZE_OCCUPIED = false; + + static final int LANDSCAPE = 0; + static final int PORTRAIT = 1; + + private static final float REORDER_HINT_MAGNITUDE = 0.12f; + private static final int REORDER_ANIMATION_DURATION = 150; + private float mReorderHintAnimationMagnitude; + + private ArrayList mIntersectingViews = new ArrayList(); + private Rect mOccupiedRect = new Rect(); + private int[] mDirectionVector = new int[2]; + int[] mPreviousReorderDirection = new int[2]; + private static final int INVALID_DIRECTION = -100; + private DropTarget.DragEnforcer mDragEnforcer; + + private final static PorterDuffXfermode sAddBlendMode = + new PorterDuffXfermode(PorterDuff.Mode.ADD); + + public CellLayout(Context context) { + this(context, null); + } + + public CellLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CellLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mDragEnforcer = new DropTarget.DragEnforcer(context); + + // 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); + mLauncher = (Launcher) context; + + 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); + mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0); + mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0); + mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0); + mCountX = LauncherModel.getCellCountX(); + mCountY = LauncherModel.getCellCountY(); + mOccupied = new boolean[mCountX][mCountY]; + mTmpOccupied = new boolean[mCountX][mCountY]; + mPreviousReorderDirection[0] = INVALID_DIRECTION; + mPreviousReorderDirection[1] = INVALID_DIRECTION; + + a.recycle(); + + setAlwaysDrawnWithCacheEnabled(false); + + final Resources res = getResources(); + + mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo); + mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo); + + mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left); + mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right); + mForegroundPadding = + res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding); + + mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE * + res.getDimensionPixelSize(R.dimen.app_icon_size)); + + mNormalBackground.setFilterBitmap(true); + mActiveGlowBackground.setFilterBitmap(true); + + // Initialize the data structures used for the drag visualization. + + mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out + + + mDragCell[0] = mDragCell[1] = -1; + for (int i = 0; i < mDragOutlines.length; i++) { + mDragOutlines[i] = new Rect(-1, -1, -1, -1); + } + + // 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) { + @SuppressWarnings("all") // suppress dead code warning + final boolean debug = false; + if (debug) { + 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(); + CellLayout.this.invalidate(mDragOutlines[thisIndex]); + } + } + }); + // 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(); + mForegroundRect = new Rect(); + + mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context); + mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap); + addView(mShortcutsAndWidgets); + } + + static int widthInPortrait(Resources r, int numCells) { + // We use this method from Workspace to figure out how many rows/columns Launcher should + // have. We ignore the left/right padding on CellLayout because it turns out in our design + // the padding extends outside the visible screen size, but it looked fine anyway. + int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); + int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap), + r.getDimensionPixelSize(R.dimen.workspace_height_gap)); + + return minGap * (numCells - 1) + cellWidth * numCells; + } + + static int heightInLandscape(Resources r, int numCells) { + // We use this method from Workspace to figure out how many rows/columns Launcher should + // have. We ignore the left/right padding on CellLayout because it turns out in our design + // the padding extends outside the visible screen size, but it looked fine anyway. + int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); + int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap), + r.getDimensionPixelSize(R.dimen.workspace_height_gap)); + + return minGap * (numCells - 1) + cellHeight * numCells; + } + + public void enableHardwareLayers() { + mShortcutsAndWidgets.enableHardwareLayers(); + } + + public void setGridSize(int x, int y) { + mCountX = x; + mCountY = y; + mOccupied = new boolean[mCountX][mCountY]; + mTmpOccupied = new boolean[mCountX][mCountY]; + mTempRectStack.clear(); + requestLayout(); + } + + private void invalidateBubbleTextView(BubbleTextView icon) { + final int padding = icon.getPressedOrFocusedBackgroundPadding(); + invalidate(icon.getLeft() + getPaddingLeft() - padding, + icon.getTop() + getPaddingTop() - padding, + icon.getRight() + getPaddingLeft() + padding, + icon.getBottom() + getPaddingTop() + padding); + } + + void setOverScrollAmount(float r, boolean left) { + if (left && mOverScrollForegroundDrawable != mOverScrollLeft) { + mOverScrollForegroundDrawable = mOverScrollLeft; + } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) { + mOverScrollForegroundDrawable = mOverScrollRight; + } + + mForegroundAlpha = (int) Math.round((r * 255)); + mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha); + invalidate(); + } + + void setPressedOrFocusedIcon(BubbleTextView icon) { + // We draw the pressed or focused BubbleTextView's background in CellLayout because it + // requires an expanded clip rect (due to the glow's blur radius) + BubbleTextView oldIcon = mPressedOrFocusedIcon; + mPressedOrFocusedIcon = icon; + if (oldIcon != null) { + invalidateBubbleTextView(oldIcon); + } + if (mPressedOrFocusedIcon != null) { + invalidateBubbleTextView(mPressedOrFocusedIcon); + } + } + + void setIsDragOverlapping(boolean isDragOverlapping) { + if (mIsDragOverlapping != isDragOverlapping) { + mIsDragOverlapping = isDragOverlapping; + invalidate(); + } + } + + boolean getIsDragOverlapping() { + return mIsDragOverlapping; + } + + protected void setOverscrollTransformsDirty(boolean dirty) { + mScrollingTransformsDirty = dirty; + } + + protected void resetOverscrollTransforms() { + if (mScrollingTransformsDirty) { + setOverscrollTransformsDirty(false); + setTranslationX(0); + setRotationY(0); + // It doesn't matter if we pass true or false here, the important thing is that we + // pass 0, which results in the overscroll drawable not being drawn any more. + setOverScrollAmount(0, false); + setPivotX(getMeasuredWidth() / 2); + setPivotY(getMeasuredHeight() / 2); + } + } + + @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 (mIsDragOverlapping) { + // In the mini case, we draw the active_glow bg *over* the active background + bg = mActiveGlowBackground; + } else { + bg = mNormalBackground; + } + + bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255)); + bg.setBounds(mBackgroundRect); + bg.draw(canvas); + } + + final Paint paint = mDragOutlinePaint; + for (int i = 0; i < mDragOutlines.length; i++) { + final float alpha = mDragOutlineAlphas[i]; + if (alpha > 0) { + final Rect r = mDragOutlines[i]; + final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); + paint.setAlpha((int)(alpha + .5f)); + canvas.drawBitmap(b, null, r, paint); + } + } + + // We draw the pressed or focused BubbleTextView's background in CellLayout because it + // requires an expanded clip rect (due to the glow's blur radius) + if (mPressedOrFocusedIcon != null) { + final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding(); + final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground(); + if (b != null) { + canvas.drawBitmap(b, + mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding, + mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding, + null); + } + } + + if (DEBUG_VISUALIZE_OCCUPIED) { + int[] pt = new int[2]; + ColorDrawable cd = new ColorDrawable(Color.RED); + cd.setBounds(0, 0, mCellWidth, mCellHeight); + for (int i = 0; i < mCountX; i++) { + for (int j = 0; j < mCountY; j++) { + if (mOccupied[i][j]) { + cellToPoint(i, j, pt); + canvas.save(); + canvas.translate(pt[0], pt[1]); + cd.draw(canvas); + canvas.restore(); + } + } + } + } + + int previewOffset = FolderRingAnimator.sPreviewSize; + + // The folder outer / inner ring image(s) + for (int i = 0; i < mFolderOuterRings.size(); i++) { + FolderRingAnimator fra = mFolderOuterRings.get(i); + + // Draw outer ring + Drawable d = FolderRingAnimator.sSharedOuterRingDrawable; + int width = (int) fra.getOuterRingSize(); + int height = width; + cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); + + int centerX = mTempLocation[0] + mCellWidth / 2; + int centerY = mTempLocation[1] + previewOffset / 2; + + canvas.save(); + canvas.translate(centerX - width / 2, centerY - height / 2); + d.setBounds(0, 0, width, height); + d.draw(canvas); + canvas.restore(); + + // Draw inner ring + d = FolderRingAnimator.sSharedInnerRingDrawable; + width = (int) fra.getInnerRingSize(); + height = width; + cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); + + centerX = mTempLocation[0] + mCellWidth / 2; + centerY = mTempLocation[1] + previewOffset / 2; + canvas.save(); + canvas.translate(centerX - width / 2, centerY - width / 2); + d.setBounds(0, 0, width, height); + d.draw(canvas); + canvas.restore(); + } + + if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) { + Drawable d = FolderIcon.sSharedFolderLeaveBehind; + int width = d.getIntrinsicWidth(); + int height = d.getIntrinsicHeight(); + + cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation); + int centerX = mTempLocation[0] + mCellWidth / 2; + int centerY = mTempLocation[1] + previewOffset / 2; + + canvas.save(); + canvas.translate(centerX - width / 2, centerY - width / 2); + d.setBounds(0, 0, width, height); + d.draw(canvas); + canvas.restore(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (mForegroundAlpha > 0) { + mOverScrollForegroundDrawable.setBounds(mForegroundRect); + Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint(); + p.setXfermode(sAddBlendMode); + mOverScrollForegroundDrawable.draw(canvas); + p.setXfermode(null); + } + } + + public void showFolderAccept(FolderRingAnimator fra) { + mFolderOuterRings.add(fra); + } + + public void hideFolderAccept(FolderRingAnimator fra) { + if (mFolderOuterRings.contains(fra)) { + mFolderOuterRings.remove(fra); + } + invalidate(); + } + + public void setFolderLeaveBehindCell(int x, int y) { + mFolderLeaveBehindCell[0] = x; + mFolderLeaveBehindCell[1] = y; + invalidate(); + } + + public void clearFolderLeaveBehind() { + mFolderLeaveBehindCell[0] = -1; + mFolderLeaveBehindCell[1] = -1; + invalidate(); + } + + @Override + public boolean shouldDelayChildPressedState() { + return false; + } + + @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 void setOnInterceptTouchListener(View.OnTouchListener listener) { + mInterceptTouchListener = listener; + } + + int getCountX() { + return mCountX; + } + + int getCountY() { + return mCountY; + } + + public void setIsHotseat(boolean isHotseat) { + mIsHotseat = isHotseat; + } + + public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, + boolean markCells) { + final LayoutParams lp = params; + + // Hotseat icons - remove text + if (child instanceof BubbleTextView) { + BubbleTextView bubbleChild = (BubbleTextView) child; + + Resources res = getResources(); + if (mIsHotseat) { + bubbleChild.setTextColor(res.getColor(android.R.color.transparent)); + } else { + bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color)); + } + } + + // Generate an id for each view, this assumes we have at most 256x256 cells + // per workspace screen + 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); + + mShortcutsAndWidgets.addView(child, index, lp); + + if (markCells) markCellsAsOccupiedForView(child); + + return true; + } + return false; + } + + @Override + public void removeAllViews() { + clearOccupiedCells(); + mShortcutsAndWidgets.removeAllViews(); + } + + @Override + public void removeAllViewsInLayout() { + if (mShortcutsAndWidgets.getChildCount() > 0) { + clearOccupiedCells(); + mShortcutsAndWidgets.removeAllViewsInLayout(); + } + } + + public void removeViewWithoutMarkingCells(View view) { + mShortcutsAndWidgets.removeView(view); + } + + @Override + public void removeView(View view) { + markCellsAsUnoccupiedForView(view); + mShortcutsAndWidgets.removeView(view); + } + + @Override + public void removeViewAt(int index) { + markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index)); + mShortcutsAndWidgets.removeViewAt(index); + } + + @Override + public void removeViewInLayout(View view) { + markCellsAsUnoccupiedForView(view); + mShortcutsAndWidgets.removeViewInLayout(view); + } + + @Override + public void removeViews(int start, int count) { + for (int i = start; i < start + count; i++) { + markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i)); + } + mShortcutsAndWidgets.removeViews(start, count); + } + + @Override + public void removeViewsInLayout(int start, int count) { + for (int i = start; i < start + count; i++) { + markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i)); + } + mShortcutsAndWidgets.removeViewsInLayout(start, count); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); + } + + public void setTagToCellInfoForPoint(int touchX, int touchY) { + final CellInfo cellInfo = mCellInfo; + Rect frame = mRect; + final int x = touchX + getScrollX(); + final int y = touchY + getScrollY(); + final int count = mShortcutsAndWidgets.getChildCount(); + + boolean found = false; + for (int i = count - 1; i >= 0; i--) { + final View child = mShortcutsAndWidgets.getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) && + lp.isLockedToGrid) { + child.getHitRect(frame); + + float scale = child.getScaleX(); + frame = new Rect(child.getLeft(), child.getTop(), child.getRight(), + child.getBottom()); + // The child hit rect is relative to the CellLayoutChildren parent, so we need to + // offset that by this CellLayout's padding to test an (x,y) point that is relative + // to this view. + frame.offset(getPaddingLeft(), getPaddingTop()); + frame.inset((int) (frame.width() * (1f - scale) / 2), + (int) (frame.height() * (1f - scale) / 2)); + + if (frame.contains(x, y)) { + cellInfo.cell = child; + cellInfo.cellX = lp.cellX; + cellInfo.cellY = lp.cellY; + cellInfo.spanX = lp.cellHSpan; + cellInfo.spanY = lp.cellVSpan; + found = true; + break; + } + } + } + + mLastDownOnOccupiedCell = found; + + if (!found) { + final int cellXY[] = mTmpXY; + pointToCellExact(x, y, cellXY); + + cellInfo.cell = null; + cellInfo.cellX = cellXY[0]; + cellInfo.cellY = cellXY[1]; + cellInfo.spanX = 1; + cellInfo.spanY = 1; + } + setTag(cellInfo); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // First we clear the tag to ensure that on every touch down we start with a fresh slate, + // even in the case where we return early. Not clearing here was causing bugs whereby on + // long-press we'd end up picking up an item from a previous drag operation. + final int action = ev.getAction(); + + if (action == MotionEvent.ACTION_DOWN) { + clearTagCellInfo(); + } + + if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { + return true; + } + + if (action == MotionEvent.ACTION_DOWN) { + setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); + } + + return false; + } + + private void clearTagCellInfo() { + final CellInfo cellInfo = mCellInfo; + cellInfo.cell = null; + cellInfo.cellX = -1; + cellInfo.cellY = -1; + cellInfo.spanX = 0; + cellInfo.spanY = 0; + setTag(cellInfo); + } + + public CellInfo getTag() { + return (CellInfo) super.getTag(); + } + + /** + * 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 int hStartPadding = getPaddingLeft(); + final int vStartPadding = getPaddingTop(); + + result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); + result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); + + 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 + * @param y Y coordinate of the point + * @param result Array of 2 ints to hold the x and y coordinate of the cell + */ + void pointToCellRounded(int x, int y, int[] result) { + pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result); + } + + /** + * Given a cell coordinate, return the point that represents the upper left corner of that 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 int hStartPadding = getPaddingLeft(); + final int vStartPadding = getPaddingTop(); + + result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); + result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); + } + + /** + * Given a cell coordinate, return the point that represents the center 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 cellToCenterPoint(int cellX, int cellY, int[] result) { + regionToCenterPoint(cellX, cellY, 1, 1, result); + } + + /** + * Given a cell coordinate and span return the point that represents the center of the regio + * + * @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 regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) { + final int hStartPadding = getPaddingLeft(); + final int vStartPadding = getPaddingTop(); + result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + + (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2; + result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + + (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2; + } + + /** + * Given a cell coordinate and span fills out a corresponding pixel rect + * + * @param cellX X coordinate of the cell + * @param cellY Y coordinate of the cell + * @param result Rect in which to write the result + */ + void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) { + final int hStartPadding = getPaddingLeft(); + final int vStartPadding = getPaddingTop(); + final int left = hStartPadding + cellX * (mCellWidth + mWidthGap); + final int top = vStartPadding + cellY * (mCellHeight + mHeightGap); + result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap), + top + (spanY * mCellHeight + (spanY - 1) * mHeightGap)); + } + + public float getDistanceFromCell(float x, float y, int[] cell) { + cellToCenterPoint(cell[0], cell[1], mTmpPoint); + float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) + + Math.pow(y - mTmpPoint[1], 2)); + return distance; + } + + int getCellWidth() { + return mCellWidth; + } + + int getCellHeight() { + return mCellHeight; + } + + int getWidthGap() { + return mWidthGap; + } + + int getHeightGap() { + return mHeightGap; + } + + Rect getContentRect(Rect r) { + if (r == null) { + r = new Rect(); + } + int left = getPaddingLeft(); + int top = getPaddingTop(); + int right = left + getWidth() - getPaddingLeft() - getPaddingRight(); + int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom(); + r.set(left, top, right, bottom); + return r; + } + + static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight, + int countX, int countY, int orientation) { + int numWidthGaps = countX - 1; + int numHeightGaps = countY - 1; + + int widthGap; + int heightGap; + int cellWidth; + int cellHeight; + int paddingLeft; + int paddingRight; + int paddingTop; + int paddingBottom; + + int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap); + if (orientation == LANDSCAPE) { + cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land); + cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land); + widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land); + heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land); + paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land); + paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land); + paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land); + paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land); + } else { + // PORTRAIT + cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port); + cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port); + widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port); + heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port); + paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port); + paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port); + paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port); + paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port); + } + + if (widthGap < 0 || heightGap < 0) { + int hSpace = measureWidth - paddingLeft - paddingRight; + int vSpace = measureHeight - paddingTop - paddingBottom; + int hFreeSpace = hSpace - (countX * cellWidth); + int vFreeSpace = vSpace - (countY * cellHeight); + widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); + heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); + } + metrics.set(cellWidth, cellHeight, widthGap, heightGap); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + 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"); + } + + int numWidthGaps = mCountX - 1; + int numHeightGaps = mCountY - 1; + + if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { + int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight(); + int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); + int hFreeSpace = hSpace - (mCountX * mCellWidth); + int vFreeSpace = vSpace - (mCountY * mCellHeight); + mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); + mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); + mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap); + } else { + mWidthGap = mOriginalWidthGap; + mHeightGap = mOriginalHeightGap; + } + + // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY + int newWidth = widthSpecSize; + int newHeight = heightSpecSize; + if (widthSpecMode == MeasureSpec.AT_MOST) { + newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) + + ((mCountX - 1) * mWidthGap); + newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) + + ((mCountY - 1) * mHeightGap); + setMeasuredDimension(newWidth, newHeight); + } + + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() - + getPaddingRight(), MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() - + getPaddingBottom(), 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(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.layout(getPaddingLeft(), getPaddingTop(), + r - l - getPaddingRight(), b - t - getPaddingBottom()); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mBackgroundRect.set(0, 0, w, h); + mForegroundRect.set(mForegroundPadding, mForegroundPadding, + w - 2 * mForegroundPadding, h - 2 * mForegroundPadding); + } + + @Override + protected void setChildrenDrawingCacheEnabled(boolean enabled) { + mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled); + } + + @Override + protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { + mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled); + } + + public float getBackgroundAlpha() { + return mBackgroundAlpha; + } + + public void setBackgroundAlphaMultiplier(float multiplier) { + if (mBackgroundAlphaMultiplier != multiplier) { + mBackgroundAlphaMultiplier = multiplier; + invalidate(); + } + } + + public float getBackgroundAlphaMultiplier() { + return mBackgroundAlphaMultiplier; + } + + public void setBackgroundAlpha(float alpha) { + if (mBackgroundAlpha != alpha) { + mBackgroundAlpha = alpha; + invalidate(); + } + } + + public void setShortcutAndWidgetAlpha(float alpha) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + getChildAt(i).setAlpha(alpha); + } + } + + public ShortcutAndWidgetContainer getShortcutsAndWidgets() { + if (getChildCount() > 0) { + return (ShortcutAndWidgetContainer) getChildAt(0); + } + return null; + } + + public View getChildAt(int x, int y) { + return mShortcutsAndWidgets.getChildAt(x, y); + } + + public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, + int delay, boolean permanent, boolean adjustOccupied) { + ShortcutAndWidgetContainer clc = getShortcutsAndWidgets(); + boolean[][] occupied = mOccupied; + if (!permanent) { + occupied = mTmpOccupied; + } + + if (clc.indexOfChild(child) != -1) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final ItemInfo info = (ItemInfo) child.getTag(); + + // We cancel any existing animations + if (mReorderAnimators.containsKey(lp)) { + mReorderAnimators.get(lp).cancel(); + mReorderAnimators.remove(lp); + } + + final int oldX = lp.x; + final int oldY = lp.y; + if (adjustOccupied) { + occupied[lp.cellX][lp.cellY] = false; + occupied[cellX][cellY] = true; + } + lp.isLockedToGrid = true; + if (permanent) { + lp.cellX = info.cellX = cellX; + lp.cellY = info.cellY = cellY; + } else { + lp.tmpCellX = cellX; + lp.tmpCellY = cellY; + } + clc.setupLp(lp); + lp.isLockedToGrid = false; + final int newX = lp.x; + final int newY = lp.y; + + lp.x = oldX; + lp.y = oldY; + + // Exit early if we're not actually moving the view + if (oldX == newX && oldY == newY) { + lp.isLockedToGrid = true; + return true; + } + + ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + va.setDuration(duration); + mReorderAnimators.put(lp, va); + + va.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float r = ((Float) animation.getAnimatedValue()).floatValue(); + lp.x = (int) ((1 - r) * oldX + r * newX); + lp.y = (int) ((1 - r) * oldY + r * newY); + child.requestLayout(); + } + }); + va.addListener(new AnimatorListenerAdapter() { + boolean cancelled = false; + public void onAnimationEnd(Animator animation) { + // If the animation was cancelled, it means that another animation + // has interrupted this one, and we don't want to lock the item into + // place just yet. + if (!cancelled) { + lp.isLockedToGrid = true; + child.requestLayout(); + } + if (mReorderAnimators.containsKey(lp)) { + mReorderAnimators.remove(lp); + } + } + public void onAnimationCancel(Animator animation) { + cancelled = true; + } + }); + va.setStartDelay(delay); + va.start(); + return true; + } + return false; + } + + /** + * 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 cellX, + int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) { + final int oldDragCellX = mDragCell[0]; + final int oldDragCellY = mDragCell[1]; + + if (v != null && dragOffset == null) { + mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2)); + } else { + mDragCenter.set(originX, originY); + } + + if (dragOutline == null && v == null) { + return; + } + + if (cellX != oldDragCellX || cellY != oldDragCellY) { + mDragCell[0] = cellX; + mDragCell[1] = cellY; + // Find the top left corner of the rect the object will occupy + final int[] topLeft = mTmpPoint; + cellToPoint(cellX, cellY, topLeft); + + int left = topLeft[0]; + int top = topLeft[1]; + + if (v != null && dragOffset == null) { + // When drawing the drag outline, it did not account for margin offsets + // added by the view's parent. + MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); + left += lp.leftMargin; + top += lp.topMargin; + + // Offsets due to the size difference between the View and the dragOutline. + // There is a size difference to account for the outer blur, which may lie + // outside the bounds of the view. + top += (v.getHeight() - dragOutline.getHeight()) / 2; + // We center about the x axis + left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragOutline.getWidth()) / 2; + } else { + if (dragOffset != null && dragRegion != null) { + // Center the drag region *horizontally* in the cell and apply a drag + // outline offset + left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragRegion.width()) / 2; + top += dragOffset.y; + } else { + // Center the drag outline in the cell + left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragOutline.getWidth()) / 2; + top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) + - dragOutline.getHeight()) / 2; + } + } + final int oldIndex = mDragOutlineCurrent; + mDragOutlineAnims[oldIndex].animateOut(); + mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; + Rect r = mDragOutlines[mDragOutlineCurrent]; + r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); + if (resize) { + cellToRect(cellX, cellY, spanX, spanY, r); + } + + mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); + mDragOutlineAnims[mDragOutlineCurrent].animateIn(); + } + } + + public void clearDragOutlines() { + final int oldIndex = mDragOutlineCurrent; + mDragOutlineAnims[oldIndex].animateOut(); + mDragCell[0] = mDragCell[1] = -1; + } + + /** + * 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 minSpanX The minimum horizontal span required + * @param minSpanY The minimum vertical span required + * @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 minSpanX, int minSpanY, int spanX, + int spanY, int[] result, int[] resultSpan) { + return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, + result, resultSpan); + } + + /** + * 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 ignoreOccupied If true, the result can be an occupied cell + * @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[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, + boolean ignoreOccupied, int[] result) { + return findNearestArea(pixelX, pixelY, spanX, spanY, + spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied); + } + + private final Stack mTempRectStack = new Stack(); + private void lazyInitTempRectStack() { + if (mTempRectStack.isEmpty()) { + for (int i = 0; i < mCountX * mCountY; i++) { + mTempRectStack.push(new Rect()); + } + } + } + + private void recycleTempRects(Stack used) { + while (!used.isEmpty()) { + mTempRectStack.push(used.pop()); + } + } + + /** + * 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 minSpanX The minimum horizontal span required + * @param minSpanY The minimum vertical span required + * @param spanX Horizontal span of the object. + * @param spanY Vertical span of the object. + * @param ignoreOccupied If true, the result can be an occupied cell + * @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[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, + View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan, + boolean[][] occupied) { + lazyInitTempRectStack(); + // mark space take by ignoreView as available (method checks if ignoreView is null) + markCellsAsUnoccupiedForView(ignoreView, occupied); + + // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds + // to the center of the item, but we are searching based on the top-left cell, so + // we translate the point over to correspond to the top-left. + pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f; + pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f; + + // Keep track of best-scoring drop area + final int[] bestXY = result != null ? result : new int[2]; + double bestDistance = Double.MAX_VALUE; + final Rect bestRect = new Rect(-1, -1, -1, -1); + final Stack validRegions = new Stack(); + + final int countX = mCountX; + final int countY = mCountY; + + if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 || + spanX < minSpanX || spanY < minSpanY) { + return bestXY; + } + + for (int y = 0; y < countY - (minSpanY - 1); y++) { + inner: + for (int x = 0; x < countX - (minSpanX - 1); x++) { + int ySize = -1; + int xSize = -1; + if (ignoreOccupied) { + // First, let's see if this thing fits anywhere + for (int i = 0; i < minSpanX; i++) { + for (int j = 0; j < minSpanY; j++) { + if (occupied[x + i][y + j]) { + continue inner; + } + } + } + xSize = minSpanX; + ySize = minSpanY; + + // We know that the item will fit at _some_ acceptable size, now let's see + // how big we can make it. We'll alternate between incrementing x and y spans + // until we hit a limit. + boolean incX = true; + boolean hitMaxX = xSize >= spanX; + boolean hitMaxY = ySize >= spanY; + while (!(hitMaxX && hitMaxY)) { + if (incX && !hitMaxX) { + for (int j = 0; j < ySize; j++) { + if (x + xSize > countX -1 || occupied[x + xSize][y + j]) { + // We can't move out horizontally + hitMaxX = true; + } + } + if (!hitMaxX) { + xSize++; + } + } else if (!hitMaxY) { + for (int i = 0; i < xSize; i++) { + if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) { + // We can't move out vertically + hitMaxY = true; + } + } + if (!hitMaxY) { + ySize++; + } + } + hitMaxX |= xSize >= spanX; + hitMaxY |= ySize >= spanY; + incX = !incX; + } + incX = true; + hitMaxX = xSize >= spanX; + hitMaxY = ySize >= spanY; + } + final int[] cellXY = mTmpXY; + cellToCenterPoint(x, y, cellXY); + + // We verify that the current rect is not a sub-rect of any of our previous + // candidates. In this case, the current rect is disqualified in favour of the + // containing rect. + Rect currentRect = mTempRectStack.pop(); + currentRect.set(x, y, x + xSize, y + ySize); + boolean contained = false; + for (Rect r : validRegions) { + if (r.contains(currentRect)) { + contained = true; + break; + } + } + validRegions.push(currentRect); + double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) + + Math.pow(cellXY[1] - pixelY, 2)); + + if ((distance <= bestDistance && !contained) || + currentRect.contains(bestRect)) { + bestDistance = distance; + bestXY[0] = x; + bestXY[1] = y; + if (resultSpan != null) { + resultSpan[0] = xSize; + resultSpan[1] = ySize; + } + bestRect.set(currentRect); + } + } + } + // re-mark space taken by ignoreView as occupied + markCellsAsOccupiedForView(ignoreView, occupied); + + // Return -1, -1 if no suitable location found + if (bestDistance == Double.MAX_VALUE) { + bestXY[0] = -1; + bestXY[1] = -1; + } + recycleTempRects(validRegions); + return bestXY; + } + + /** + * Find a vacant area that will fit the given bounds nearest the requested + * cell location, and will also weigh in a suggested direction vector of the + * desired location. This method computers distance based on unit grid distances, + * not pixel distances. + * + * @param cellX The X cell nearest to which you want to search for a vacant area. + * @param cellY The Y cell nearest which you want to search for a vacant area. + * @param spanX Horizontal span of the object. + * @param spanY Vertical span of the object. + * @param direction The favored direction in which the views should move from x, y + * @param exactDirectionOnly If this parameter is true, then only solutions where the direction + * matches exactly. Otherwise we find the best matching direction. + * @param occoupied The array which represents which cells in the CellLayout are occupied + * @param blockOccupied The array which represents which cells in the specified block (cellX, + * cellY, spanX, spanY) are occupied. This is used when try to move a group of views. + * @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. + */ + private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, + boolean[][] occupied, boolean blockOccupied[][], int[] result) { + // Keep track of best-scoring drop area + final int[] bestXY = result != null ? result : new int[2]; + float bestDistance = Float.MAX_VALUE; + int bestDirectionScore = Integer.MIN_VALUE; + + final int countX = mCountX; + final int countY = mCountY; + + for (int y = 0; y < countY - (spanY - 1); y++) { + inner: + for (int x = 0; x < countX - (spanX - 1); x++) { + // First, let's see if this thing fits anywhere + for (int i = 0; i < spanX; i++) { + for (int j = 0; j < spanY; j++) { + if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) { + continue inner; + } + } + } + + float distance = (float) + Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); + int[] curDirection = mTmpPoint; + computeDirectionVector(x - cellX, y - cellY, curDirection); + // The direction score is just the dot product of the two candidate direction + // and that passed in. + int curDirectionScore = direction[0] * curDirection[0] + + direction[1] * curDirection[1]; + boolean exactDirectionOnly = false; + boolean directionMatches = direction[0] == curDirection[0] && + direction[0] == curDirection[0]; + if ((directionMatches || !exactDirectionOnly) && + Float.compare(distance, bestDistance) < 0 || (Float.compare(distance, + bestDistance) == 0 && curDirectionScore > bestDirectionScore)) { + bestDistance = distance; + bestDirectionScore = curDirectionScore; + bestXY[0] = x; + bestXY[1] = y; + } + } + } + + // Return -1, -1 if no suitable location found + if (bestDistance == Float.MAX_VALUE) { + bestXY[0] = -1; + bestXY[1] = -1; + } + return bestXY; + } + + private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY, + int[] direction,boolean[][] occupied, + boolean blockOccupied[][], int[] result) { + // Keep track of best-scoring drop area + final int[] bestXY = result != null ? result : new int[2]; + bestXY[0] = -1; + bestXY[1] = -1; + float bestDistance = Float.MAX_VALUE; + + // We use this to march in a single direction + if ((direction[0] != 0 && direction[1] != 0) || + (direction[0] == 0 && direction[1] == 0)) { + return bestXY; + } + + // This will only incrememnet one of x or y based on the assertion above + int x = cellX + direction[0]; + int y = cellY + direction[1]; + while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) { + + boolean fail = false; + for (int i = 0; i < spanX; i++) { + for (int j = 0; j < spanY; j++) { + if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) { + fail = true; + } + } + } + if (!fail) { + float distance = (float) + Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); + if (Float.compare(distance, bestDistance) < 0) { + bestDistance = distance; + bestXY[0] = x; + bestXY[1] = y; + } + } + x += direction[0]; + y += direction[1]; + } + return bestXY; + } + + private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, + int[] direction, ItemConfiguration currentState) { + CellAndSpan c = currentState.map.get(v); + boolean success = false; + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); + markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); + + findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation); + + if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { + c.x = mTempLocation[0]; + c.y = mTempLocation[1]; + success = true; + + } + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); + return success; + } + + // This method looks in the specified direction to see if there is an additional view + // immediately adjecent in that direction + private boolean addViewInDirection(ArrayList views, Rect boundingRect, int[] direction, + boolean[][] occupied, View dragView, ItemConfiguration currentState) { + boolean found = false; + + int childCount = mShortcutsAndWidgets.getChildCount(); + Rect r0 = new Rect(boundingRect); + Rect r1 = new Rect(); + + int deltaX = 0; + int deltaY = 0; + if (direction[1] < 0) { + r0.set(r0.left, r0.top - 1, r0.right, r0.bottom); + deltaY = -1; + } else if (direction[1] > 0) { + r0.set(r0.left, r0.top, r0.right, r0.bottom + 1); + deltaY = 1; + } else if (direction[0] < 0) { + r0.set(r0.left - 1, r0.top, r0.right, r0.bottom); + deltaX = -1; + } else if (direction[0] > 0) { + r0.set(r0.left, r0.top, r0.right + 1, r0.bottom); + deltaX = 1; + } + + for (int i = 0; i < childCount; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + if (views.contains(child) || child == dragView) continue; + CellAndSpan c = currentState.map.get(child); + + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); + if (Rect.intersects(r0, r1)) { + if (!lp.canReorder) { + return false; + } + boolean pushed = false; + for (int x = c.x; x < c.x + c.spanX; x++) { + for (int y = c.y; y < c.y + c.spanY; y++) { + boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX + && y - deltaY >= 0 && y - deltaY < mCountY; + if (inBounds && occupied[x - deltaX][y - deltaY]) { + pushed = true; + } + } + } + if (pushed) { + views.add(child); + boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY); + found = true; + } + } + } + return found; + } + + private boolean addViewsToTempLocation(ArrayList views, Rect rectOccupiedByPotentialDrop, + int[] direction, boolean push, View dragView, ItemConfiguration currentState) { + if (views.size() == 0) return true; + + boolean success = false; + Rect boundingRect = null; + // We construct a rect which represents the entire group of views passed in + for (View v: views) { + CellAndSpan c = currentState.map.get(v); + if (boundingRect == null) { + boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY); + } else { + boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY); + } + } + + @SuppressWarnings("unchecked") + ArrayList dup = (ArrayList) views.clone(); + // We try and expand the group of views in the direction vector passed, based on + // whether they are physically adjacent, ie. based on "push mechanics". + while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView, + currentState)) { + } + + // Mark the occupied state as false for the group of views we want to move. + for (View v: dup) { + CellAndSpan c = currentState.map.get(v); + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); + } + + boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()]; + int top = boundingRect.top; + int left = boundingRect.left; + // We mark more precisely which parts of the bounding rect are truly occupied, allowing + // for tetris-style interlocking. + for (View v: dup) { + CellAndSpan c = currentState.map.get(v); + markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true); + } + + markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); + + if (push) { + findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(), + boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); + } else { + findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), + boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); + } + + // If we successfuly found a location by pushing the block of views, we commit it + if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { + int deltaX = mTempLocation[0] - boundingRect.left; + int deltaY = mTempLocation[1] - boundingRect.top; + for (View v: dup) { + CellAndSpan c = currentState.map.get(v); + c.x += deltaX; + c.y += deltaY; + } + success = true; + } + + // In either case, we set the occupied array as marked for the location of the views + for (View v: dup) { + CellAndSpan c = currentState.map.get(v); + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); + } + return success; + } + + private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) { + markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value); + } + + // This method tries to find a reordering solution which satisfies the push mechanic by trying + // to push items in each of the cardinal directions, in an order based on the direction vector + // passed. + private boolean attemptPushInDirection(ArrayList intersectingViews, Rect occupied, + int[] direction, View ignoreView, ItemConfiguration solution) { + if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) { + // If the direction vector has two non-zero components, we try pushing + // separately in each of the components. + int temp = direction[1]; + direction[1] = 0; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + direction[1] = temp; + temp = direction[0]; + direction[0] = 0; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + // Revert the direction + direction[0] = temp; + + // Now we try pushing in each component of the opposite direction + direction[0] *= -1; + direction[1] *= -1; + temp = direction[1]; + direction[1] = 0; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + + direction[1] = temp; + temp = direction[0]; + direction[0] = 0; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + // revert the direction + direction[0] = temp; + direction[0] *= -1; + direction[1] *= -1; + + } else { + // If the direction vector has a single non-zero component, we push first in the + // direction of the vector + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + + // Then we try the opposite direction + direction[0] *= -1; + direction[1] *= -1; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + // Switch the direction back + direction[0] *= -1; + direction[1] *= -1; + + // If we have failed to find a push solution with the above, then we try + // to find a solution by pushing along the perpendicular axis. + + // Swap the components + int temp = direction[1]; + direction[1] = direction[0]; + direction[0] = temp; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + + // Then we try the opposite direction + direction[0] *= -1; + direction[1] *= -1; + if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + ignoreView, solution)) { + return true; + } + // Switch the direction back + direction[0] *= -1; + direction[1] *= -1; + + // Swap the components back + temp = direction[1]; + direction[1] = direction[0]; + direction[0] = temp; + } + return false; + } + + private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, + View ignoreView, ItemConfiguration solution) { + // Return early if get invalid cell positions + if (cellX < 0 || cellY < 0) return false; + + mIntersectingViews.clear(); + mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY); + + // Mark the desired location of the view currently being dragged. + if (ignoreView != null) { + CellAndSpan c = solution.map.get(ignoreView); + if (c != null) { + c.x = cellX; + c.y = cellY; + } + } + Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); + Rect r1 = new Rect(); + for (View child: solution.map.keySet()) { + if (child == ignoreView) continue; + CellAndSpan c = solution.map.get(child); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); + if (Rect.intersects(r0, r1)) { + if (!lp.canReorder) { + return false; + } + mIntersectingViews.add(child); + } + } + + // First we try to find a solution which respects the push mechanic. That is, + // we try to find a solution such that no displaced item travels through another item + // without also displacing that item. + if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView, + solution)) { + return true; + } + + // Next we try moving the views as a block, but without requiring the push mechanic. + if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView, + solution)) { + return true; + } + + // Ok, they couldn't move as a block, let's move them individually + for (View v : mIntersectingViews) { + if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) { + return false; + } + } + return true; + } + + /* + * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between + * the provided point and the provided cell + */ + private void computeDirectionVector(float deltaX, float deltaY, int[] result) { + double angle = Math.atan(((float) deltaY) / deltaX); + + result[0] = 0; + result[1] = 0; + if (Math.abs(Math.cos(angle)) > 0.5f) { + result[0] = (int) Math.signum(deltaX); + } + if (Math.abs(Math.sin(angle)) > 0.5f) { + result[1] = (int) Math.signum(deltaY); + } + } + + private void copyOccupiedArray(boolean[][] occupied) { + for (int i = 0; i < mCountX; i++) { + for (int j = 0; j < mCountY; j++) { + occupied[i][j] = mOccupied[i][j]; + } + } + } + + ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, + int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) { + // Copy the current state into the solution. This solution will be manipulated as necessary. + copyCurrentStateToSolution(solution, false); + // Copy the current occupied array into the temporary occupied array. This array will be + // manipulated as necessary to find a solution. + copyOccupiedArray(mTmpOccupied); + + // We find the nearest cell into which we would place the dragged item, assuming there's + // nothing in its way. + int result[] = new int[2]; + result = findNearestArea(pixelX, pixelY, spanX, spanY, result); + + boolean success = false; + // First we try the exact nearest position of the item being dragged, + // we will then want to try to move this around to other neighbouring positions + success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView, + solution); + + if (!success) { + // We try shrinking the widget down to size in an alternating pattern, shrink 1 in + // x, then 1 in y etc. + if (spanX > minSpanX && (minSpanY == spanY || decX)) { + return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction, + dragView, false, solution); + } else if (spanY > minSpanY) { + return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction, + dragView, true, solution); + } + solution.isSolution = false; + } else { + solution.isSolution = true; + solution.dragViewX = result[0]; + solution.dragViewY = result[1]; + solution.dragViewSpanX = spanX; + solution.dragViewSpanY = spanY; + } + return solution; + } + + private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { + int childCount = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + CellAndSpan c; + if (temp) { + c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan); + } else { + c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan); + } + solution.map.put(child, c); + } + } + + private void copySolutionToTempState(ItemConfiguration solution, View dragView) { + for (int i = 0; i < mCountX; i++) { + for (int j = 0; j < mCountY; j++) { + mTmpOccupied[i][j] = false; + } + } + + int childCount = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + if (child == dragView) continue; + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + CellAndSpan c = solution.map.get(child); + if (c != null) { + lp.tmpCellX = c.x; + lp.tmpCellY = c.y; + lp.cellHSpan = c.spanX; + lp.cellVSpan = c.spanY; + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); + } + } + markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, + solution.dragViewSpanY, mTmpOccupied, true); + } + + private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean + commitDragView) { + + boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied; + for (int i = 0; i < mCountX; i++) { + for (int j = 0; j < mCountY; j++) { + occupied[i][j] = false; + } + } + + int childCount = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + if (child == dragView) continue; + CellAndSpan c = solution.map.get(child); + if (c != null) { + animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0, + DESTRUCTIVE_REORDER, false); + markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true); + } + } + if (commitDragView) { + markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, + solution.dragViewSpanY, occupied, true); + } + } + + // This method starts or changes the reorder hint animations + private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) { + int childCount = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + if (child == dragView) continue; + CellAndSpan c = solution.map.get(child); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (c != null) { + ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY, + c.x, c.y, c.spanX, c.spanY); + rha.animate(); + } + } + } + + // Class which represents the reorder hint animations. These animations show that an item is + // in a temporary state, and hint at where the item will return to. + class ReorderHintAnimation { + View child; + float finalDeltaX; + float finalDeltaY; + float initDeltaX; + float initDeltaY; + float finalScale; + float initScale; + private static final int DURATION = 300; + Animator a; + + public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1, + int spanX, int spanY) { + regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint); + final int x0 = mTmpPoint[0]; + final int y0 = mTmpPoint[1]; + regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint); + final int x1 = mTmpPoint[0]; + final int y1 = mTmpPoint[1]; + final int dX = x1 - x0; + final int dY = y1 - y0; + finalDeltaX = 0; + finalDeltaY = 0; + if (dX == dY && dX == 0) { + } else { + if (dY == 0) { + finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude; + } else if (dX == 0) { + finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude; + } else { + double angle = Math.atan( (float) (dY) / dX); + finalDeltaX = (int) (- Math.signum(dX) * + Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude)); + finalDeltaY = (int) (- Math.signum(dY) * + Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude)); + } + } + initDeltaX = child.getTranslationX(); + initDeltaY = child.getTranslationY(); + finalScale = 1.0f - 4.0f / child.getWidth(); + initScale = child.getScaleX(); + + child.setPivotY(child.getMeasuredHeight() * 0.5f); + child.setPivotX(child.getMeasuredWidth() * 0.5f); + this.child = child; + } + + void animate() { + if (mShakeAnimators.containsKey(child)) { + ReorderHintAnimation oldAnimation = mShakeAnimators.get(child); + oldAnimation.cancel(); + mShakeAnimators.remove(child); + if (finalDeltaX == 0 && finalDeltaY == 0) { + completeAnimationImmediately(); + return; + } + } + if (finalDeltaX == 0 && finalDeltaY == 0) { + return; + } + ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + a = va; + va.setRepeatMode(ValueAnimator.REVERSE); + va.setRepeatCount(ValueAnimator.INFINITE); + va.setDuration(DURATION); + va.setStartDelay((int) (Math.random() * 60)); + va.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float r = ((Float) animation.getAnimatedValue()).floatValue(); + float x = r * finalDeltaX + (1 - r) * initDeltaX; + float y = r * finalDeltaY + (1 - r) * initDeltaY; + child.setTranslationX(x); + child.setTranslationY(y); + float s = r * finalScale + (1 - r) * initScale; + child.setScaleX(s); + child.setScaleY(s); + } + }); + va.addListener(new AnimatorListenerAdapter() { + public void onAnimationRepeat(Animator animation) { + // We make sure to end only after a full period + initDeltaX = 0; + initDeltaY = 0; + initScale = 1.0f; + } + }); + mShakeAnimators.put(child, this); + va.start(); + } + + private void cancel() { + if (a != null) { + a.cancel(); + } + } + + private void completeAnimationImmediately() { + if (a != null) { + a.cancel(); + } + + AnimatorSet s = new AnimatorSet(); + a = s; + s.playTogether( + ObjectAnimator.ofFloat(child, "scaleX", 1f), + ObjectAnimator.ofFloat(child, "scaleY", 1f), + ObjectAnimator.ofFloat(child, "translationX", 0f), + ObjectAnimator.ofFloat(child, "translationY", 0f) + ); + s.setDuration(REORDER_ANIMATION_DURATION); + s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); + s.start(); + } + } + + private void completeAndClearReorderHintAnimations() { + for (ReorderHintAnimation a: mShakeAnimators.values()) { + a.completeAnimationImmediately(); + } + mShakeAnimators.clear(); + } + + private void commitTempPlacement() { + for (int i = 0; i < mCountX; i++) { + for (int j = 0; j < mCountY; j++) { + mOccupied[i][j] = mTmpOccupied[i][j]; + } + } + int childCount = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + ItemInfo info = (ItemInfo) child.getTag(); + // We do a null check here because the item info can be null in the case of the + // AllApps button in the hotseat. + if (info != null) { + info.cellX = lp.cellX = lp.tmpCellX; + info.cellY = lp.cellY = lp.tmpCellY; + info.spanX = lp.cellHSpan; + info.spanY = lp.cellVSpan; + } + } + mLauncher.getWorkspace().updateItemLocationsInDatabase(this); + } + + public void setUseTempCoords(boolean useTempCoords) { + int childCount = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < childCount; i++) { + LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams(); + lp.useTmpCoords = useTempCoords; + } + } + + ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY, + int spanX, int spanY, View dragView, ItemConfiguration solution) { + int[] result = new int[2]; + int[] resultSpan = new int[2]; + findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result, + resultSpan); + if (result[0] >= 0 && result[1] >= 0) { + copyCurrentStateToSolution(solution, false); + solution.dragViewX = result[0]; + solution.dragViewY = result[1]; + solution.dragViewSpanX = resultSpan[0]; + solution.dragViewSpanY = resultSpan[1]; + solution.isSolution = true; + } else { + solution.isSolution = false; + } + return solution; + } + + public void prepareChildForDrag(View child) { + markCellsAsUnoccupiedForView(child); + } + + /* This seems like it should be obvious and straight-forward, but when the direction vector + needs to match with the notion of the dragView pushing other views, we have to employ + a slightly more subtle notion of the direction vector. The question is what two points is + the vector between? The center of the dragView and its desired destination? Not quite, as + this doesn't necessarily coincide with the interaction of the dragView and items occupying + those cells. Instead we use some heuristics to often lock the vector to up, down, left + or right, which helps make pushing feel right. + */ + private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, + int spanY, View dragView, int[] resultDirection) { + int[] targetDestination = new int[2]; + + findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination); + Rect dragRect = new Rect(); + regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect); + dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY()); + + Rect dropRegionRect = new Rect(); + getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY, + dragView, dropRegionRect, mIntersectingViews); + + int dropRegionSpanX = dropRegionRect.width(); + int dropRegionSpanY = dropRegionRect.height(); + + regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(), + dropRegionRect.height(), dropRegionRect); + + int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX; + int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY; + + if (dropRegionSpanX == mCountX || spanX == mCountX) { + deltaX = 0; + } + if (dropRegionSpanY == mCountY || spanY == mCountY) { + deltaY = 0; + } + + if (deltaX == 0 && deltaY == 0) { + // No idea what to do, give a random direction. + resultDirection[0] = 1; + resultDirection[1] = 0; + } else { + computeDirectionVector(deltaX, deltaY, resultDirection); + } + } + + // For a given cell and span, fetch the set of views intersecting the region. + private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY, + View dragView, Rect boundingRect, ArrayList intersectingViews) { + if (boundingRect != null) { + boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY); + } + intersectingViews.clear(); + Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); + Rect r1 = new Rect(); + final int count = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < count; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + if (child == dragView) continue; + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan); + if (Rect.intersects(r0, r1)) { + mIntersectingViews.add(child); + if (boundingRect != null) { + boundingRect.union(r1); + } + } + } + } + + boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, + View dragView, int[] result) { + result = findNearestArea(pixelX, pixelY, spanX, spanY, result); + getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null, + mIntersectingViews); + return !mIntersectingViews.isEmpty(); + } + + void revertTempState() { + if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return; + final int count = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < count; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) { + lp.tmpCellX = lp.cellX; + lp.tmpCellY = lp.cellY; + animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION, + 0, false, false); + } + } + completeAndClearReorderHintAnimations(); + setItemPlacementDirty(false); + } + + boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, + View dragView, int[] direction, boolean commit) { + int[] pixelXY = new int[2]; + regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY); + + // First we determine if things have moved enough to cause a different layout + ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY, + spanX, spanY, direction, dragView, true, new ItemConfiguration()); + + setUseTempCoords(true); + if (swapSolution != null && swapSolution.isSolution) { + // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother + // committing anything or animating anything as we just want to determine if a solution + // exists + copySolutionToTempState(swapSolution, dragView); + setItemPlacementDirty(true); + animateItemsToSolution(swapSolution, dragView, commit); + + if (commit) { + commitTempPlacement(); + completeAndClearReorderHintAnimations(); + setItemPlacementDirty(false); + } else { + beginOrAdjustHintAnimations(swapSolution, dragView, + REORDER_ANIMATION_DURATION); + } + mShortcutsAndWidgets.requestLayout(); + } + return swapSolution.isSolution; + } + + int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, + View dragView, int[] result, int resultSpan[], int mode) { + // First we determine if things have moved enough to cause a different layout + result = findNearestArea(pixelX, pixelY, spanX, spanY, result); + + if (resultSpan == null) { + resultSpan = new int[2]; + } + + // When we are checking drop validity or actually dropping, we don't recompute the + // direction vector, since we want the solution to match the preview, and it's possible + // that the exact position of the item has changed to result in a new reordering outcome. + if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP) + && mPreviousReorderDirection[0] != INVALID_DIRECTION) { + mDirectionVector[0] = mPreviousReorderDirection[0]; + mDirectionVector[1] = mPreviousReorderDirection[1]; + // We reset this vector after drop + if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) { + mPreviousReorderDirection[0] = INVALID_DIRECTION; + mPreviousReorderDirection[1] = INVALID_DIRECTION; + } + } else { + getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector); + mPreviousReorderDirection[0] = mDirectionVector[0]; + mPreviousReorderDirection[1] = mDirectionVector[1]; + } + + ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY, + spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration()); + + // We attempt the approach which doesn't shuffle views at all + ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX, + minSpanY, spanX, spanY, dragView, new ItemConfiguration()); + + ItemConfiguration finalSolution = null; + if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) { + finalSolution = swapSolution; + } else if (noShuffleSolution.isSolution) { + finalSolution = noShuffleSolution; + } + + boolean foundSolution = true; + if (!DESTRUCTIVE_REORDER) { + setUseTempCoords(true); + } + + if (finalSolution != null) { + result[0] = finalSolution.dragViewX; + result[1] = finalSolution.dragViewY; + resultSpan[0] = finalSolution.dragViewSpanX; + resultSpan[1] = finalSolution.dragViewSpanY; + + // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother + // committing anything or animating anything as we just want to determine if a solution + // exists + if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) { + if (!DESTRUCTIVE_REORDER) { + copySolutionToTempState(finalSolution, dragView); + } + setItemPlacementDirty(true); + animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP); + + if (!DESTRUCTIVE_REORDER && + (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) { + commitTempPlacement(); + completeAndClearReorderHintAnimations(); + setItemPlacementDirty(false); + } else { + beginOrAdjustHintAnimations(finalSolution, dragView, + REORDER_ANIMATION_DURATION); + } + } + } else { + foundSolution = false; + result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1; + } + + if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) { + setUseTempCoords(false); + } + + mShortcutsAndWidgets.requestLayout(); + return result; + } + + void setItemPlacementDirty(boolean dirty) { + mItemPlacementDirty = dirty; + } + boolean isItemPlacementDirty() { + return mItemPlacementDirty; + } + + private class ItemConfiguration { + HashMap map = new HashMap(); + boolean isSolution = false; + int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY; + + int area() { + return dragViewSpanX * dragViewSpanY; + } + } + + private class CellAndSpan { + int x, y; + int spanX, spanY; + + public CellAndSpan(int x, int y, int spanX, int spanY) { + this.x = x; + this.y = y; + this.spanX = spanX; + this.spanY = spanY; + } + } + + /** + * 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 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, View ignoreView, int[] result) { + return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, 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 minSpanX The minimum horizontal span required + * @param minSpanY The minimum vertical span required + * @param spanX Horizontal span of the object. + * @param spanY Vertical span of the object. + * @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 minSpanX, int minSpanY, + int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) { + return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true, + result, resultSpan, mOccupied); + } + + /** + * Find a starting cell position 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 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[] findNearestArea( + int pixelX, int pixelY, int spanX, int spanY, int[] result) { + return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result); + } + + boolean existsEmptyCell() { + return findCellForSpan(null, 1, 1); + } + + /** + * 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, mOccupied); + } + + /** + * 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, mOccupied); + } + + /** + * 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, mOccupied); + } + + /** + * The superset of the above two methods + */ + boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, + int intersectX, int intersectY, View ignoreView, boolean occupied[][]) { + // mark space take by ignoreView as available (method checks if ignoreView is null) + markCellsAsUnoccupiedForView(ignoreView, occupied); + + 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 y = startY; y < endY && !foundCell; y++) { + inner: + for (int x = startX; x < endX; x++) { + 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 after the column we just found + // an occupied cell + x += i; + 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, occupied); + return foundCell; + } + + /** + * 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 onDragEnter() { + mDragEnforcer.onDragEnter(); + mDragging = true; + } + + /** + * Called when drag has left this CellLayout or has been completed (successfully or not) + */ + void onDragExit() { + mDragEnforcer.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; + } + + // Invalidate the drag data + mDragCell[0] = mDragCell[1] = -1; + mDragOutlineAnims[mDragOutlineCurrent].animateOut(); + mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length; + revertTempState(); + setIsDragOverlapping(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 + */ + void onDropChild(View child) { + if (child != null) { + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.dropped = true; + child.requestLayout(); + } + } + + /** + * 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 cellVSpan Height in cells + * @param resultRect Rect into which to put the results + */ + public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) { + final int cellWidth = mCellWidth; + final int cellHeight = mCellHeight; + final int widthGap = mWidthGap; + final int heightGap = mHeightGap; + + final int hStartPadding = getPaddingLeft(); + final int vStartPadding = getPaddingTop(); + + 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); + + resultRect.set(x, y, x + width, y + height); + } + + /** + * 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, 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. + int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width); + int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height); + int smallerSize = Math.min(actualWidth, actualHeight); + + // Always round up to next largest cell + int spanX = (int) Math.ceil(width / (float) smallerSize); + int spanY = (int) Math.ceil(height / (float) smallerSize); + + if (result == null) { + return new int[] { spanX, spanY }; + } + result[0] = spanX; + result[1] = spanY; + return result; + } + + public int[] cellSpansToSize(int hSpans, int vSpans) { + int[] size = new int[2]; + size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap; + size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap; + return size; + } + + /** + * Calculate the grid spans needed to fit given item + */ + public void calculateSpans(ItemInfo info) { + final int minWidth; + final int minHeight; + + if (info instanceof LauncherAppWidgetInfo) { + minWidth = ((LauncherAppWidgetInfo) info).minWidth; + minHeight = ((LauncherAppWidgetInfo) info).minHeight; + } else if (info instanceof PendingAddWidgetInfo) { + minWidth = ((PendingAddWidgetInfo) info).minWidth; + minHeight = ((PendingAddWidgetInfo) info).minHeight; + } else { + // It's not a widget, so it must be 1x1 + info.spanX = info.spanY = 1; + return; + } + int[] spans = rectToCell(minWidth, minHeight, null); + info.spanX = spans[0]; + info.spanY = spans[1]; + } + + /** + * Find the first vacant cell, if there is one. + * + * @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) { + + return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); + } + + static boolean findVacantCell(int[] vacant, int spanX, int spanY, + int xCount, int yCount, boolean[][] occupied) { + + for (int y = 0; y < yCount; y++) { + for (int x = 0; x < xCount; x++) { + boolean available = !occupied[x][y]; +out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { + for (int j = y; j < y + spanY - 1 && y < yCount; j++) { + available = available && !occupied[i][j]; + if (!available) break out; + } + } + + if (available) { + vacant[0] = x; + vacant[1] = y; + return true; + } + } + } + + return false; + } + + private void clearOccupiedCells() { + for (int x = 0; x < mCountX; x++) { + for (int y = 0; y < mCountY; y++) { + mOccupied[x][y] = false; + } + } + } + + public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) { + markCellsAsUnoccupiedForView(view); + markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true); + } + + public void markCellsAsOccupiedForView(View view) { + markCellsAsOccupiedForView(view, mOccupied); + } + public void markCellsAsOccupiedForView(View view, boolean[][] occupied) { + if (view == null || view.getParent() != mShortcutsAndWidgets) return; + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true); + } + + public void markCellsAsUnoccupiedForView(View view) { + markCellsAsUnoccupiedForView(view, mOccupied); + } + public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) { + if (view == null || view.getParent() != mShortcutsAndWidgets) return; + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false); + } + + private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied, + boolean value) { + if (cellX < 0 || cellY < 0) return; + for (int x = cellX; x < cellX + spanX && x < mCountX; x++) { + for (int y = cellY; y < cellY + spanY && y < mCountY; y++) { + occupied[x][y] = value; + } + } + } + + public int getDesiredWidth() { + return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) + + (Math.max((mCountX - 1), 0) * mWidthGap); + } + + public int getDesiredHeight() { + return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) + + (Math.max((mCountY - 1), 0) * mHeightGap); + } + + public boolean isOccupied(int x, int y) { + if (x < mCountX && y < mCountY) { + return mOccupied[x][y]; + } else { + throw new RuntimeException("Position exceeds the bound of this CellLayout"); + } + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new CellLayout.LayoutParams(getContext(), attrs); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof CellLayout.LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + 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. + */ + @ViewDebug.ExportedProperty + public int cellX; + + /** + * Vertical location of the item in the grid. + */ + @ViewDebug.ExportedProperty + public int cellY; + + /** + * Temporary horizontal location of the item in the grid during reorder + */ + public int tmpCellX; + + /** + * Temporary vertical location of the item in the grid during reorder + */ + public int tmpCellY; + + /** + * Indicates that the temporary coordinates should be used to layout the items + */ + public boolean useTmpCoords; + + /** + * 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; + + /** + * Indicates whether the item will set its x, y, width and height parameters freely, + * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan. + */ + public boolean isLockedToGrid = true; + + /** + * Indicates whether this item can be reordered. Always true except in the case of the + * the AllApps button. + */ + public boolean canReorder = true; + + // X coordinate of the view in the layout. + @ViewDebug.ExportedProperty + int x; + // Y coordinate of the view in the layout. + @ViewDebug.ExportedProperty + int y; + + boolean dropped; + + 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) { + if (isLockedToGrid) { + final int myCellHSpan = cellHSpan; + final int myCellVSpan = cellVSpan; + final int myCellX = useTmpCoords ? tmpCellX : cellX; + final int myCellY = useTmpCoords ? tmpCellY : cellY; + + width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - + leftMargin - rightMargin; + height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - + topMargin - bottomMargin; + x = (int) (myCellX * (cellWidth + widthGap) + leftMargin); + y = (int) (myCellY * (cellHeight + heightGap) + topMargin); + } + } + + public String toString() { + return "(" + this.cellX + ", " + this.cellY + ")"; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getWidth() { + return width; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getHeight() { + return height; + } + + public void setX(int x) { + this.x = x; + } + + public int getX() { + return x; + } + + public void setY(int y) { + this.y = y; + } + + public int getY() { + return y; + } + } + + // 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 { + View cell; + int cellX = -1; + int cellY = -1; + int spanX; + int spanY; + int screen; + long container; + + @Override + public String toString() { + return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + + ", x=" + cellX + ", y=" + cellY + "]"; + } + } + + public boolean lastDownOnOccupiedCell() { + return mLastDownOnOccupiedCell; + } +} diff --git a/src/com/cyanogenmod/trebuchet/CheckLongPressHelper.java b/src/com/cyanogenmod/trebuchet/CheckLongPressHelper.java new file mode 100644 index 000000000..4b17fee4c --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/CheckLongPressHelper.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 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.cyanogenmod.trebuchet; + +import android.view.View; + +public class CheckLongPressHelper { + private View mView; + private boolean mHasPerformedLongPress; + private CheckForLongPress mPendingCheckForLongPress; + + class CheckForLongPress implements Runnable { + public void run() { + if ((mView.getParent() != null) && mView.hasWindowFocus() + && !mHasPerformedLongPress) { + if (mView.performLongClick()) { + mView.setPressed(false); + mHasPerformedLongPress = true; + } + } + } + } + + public CheckLongPressHelper(View v) { + mView = v; + } + + public void postCheckForLongPress() { + mHasPerformedLongPress = false; + + if (mPendingCheckForLongPress == null) { + mPendingCheckForLongPress = new CheckForLongPress(); + } + mView.postDelayed(mPendingCheckForLongPress, LauncherApplication.getLongPressTimeout()); + } + + public void cancelLongPress() { + mHasPerformedLongPress = false; + if (mPendingCheckForLongPress != null) { + mView.removeCallbacks(mPendingCheckForLongPress); + mPendingCheckForLongPress = null; + } + } + + public boolean hasPerformedLongPress() { + return mHasPerformedLongPress; + } +} diff --git a/src/com/cyanogenmod/trebuchet/Cling.java b/src/com/cyanogenmod/trebuchet/Cling.java new file mode 100644 index 000000000..41704a5ba --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Cling.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +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.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.FocusFinder; +import android.view.MotionEvent; +import android.view.View; +import android.view.accessibility.AccessibilityManager; +import android.widget.FrameLayout; + +import com.cyanogenmod.trebuchet.R; + +public class Cling extends FrameLayout { + + static final String WORKSPACE_CLING_DISMISSED_KEY = "cling.workspace.dismissed"; + static final String ALLAPPS_CLING_DISMISSED_KEY = "cling.allapps.dismissed"; + static final String FOLDER_CLING_DISMISSED_KEY = "cling.folder.dismissed"; + + private static String WORKSPACE_PORTRAIT = "workspace_portrait"; + private static String WORKSPACE_LANDSCAPE = "workspace_landscape"; + private static String WORKSPACE_LARGE = "workspace_large"; + private static String WORKSPACE_CUSTOM = "workspace_custom"; + + private static String ALLAPPS_PORTRAIT = "all_apps_portrait"; + private static String ALLAPPS_LANDSCAPE = "all_apps_landscape"; + private static String ALLAPPS_LARGE = "all_apps_large"; + + private static String FOLDER_PORTRAIT = "folder_portrait"; + private static String FOLDER_LANDSCAPE = "folder_landscape"; + private static String FOLDER_LARGE = "folder_large"; + + private Launcher mLauncher; + private boolean mIsInitialized; + private String mDrawIdentifier; + private Drawable mBackground; + private Drawable mPunchThroughGraphic; + private Drawable mHandTouchGraphic; + private int mPunchThroughGraphicCenterRadius; + private int mAppIconSize; + private int mButtonBarHeight; + private float mRevealRadius; + private int[] mPositionData; + + private Paint mErasePaint; + + public Cling(Context context) { + this(context, null, 0); + } + + public Cling(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Cling(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0); + mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier); + a.recycle(); + } + + void init(Launcher l, int[] positionData) { + if (!mIsInitialized) { + mLauncher = l; + mPositionData = positionData; + + Resources r = getContext().getResources(); + + mPunchThroughGraphic = r.getDrawable(R.drawable.cling); + mPunchThroughGraphicCenterRadius = + r.getDimensionPixelSize(R.dimen.clingPunchThroughGraphicCenterRadius); + mAppIconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); + mRevealRadius = r.getDimensionPixelSize(R.dimen.reveal_radius) * 1f; + mButtonBarHeight = r.getDimensionPixelSize(R.dimen.button_bar_height); + + mErasePaint = new Paint(); + mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + mErasePaint.setColor(0xFFFFFF); + mErasePaint.setAlpha(0); + + mIsInitialized = true; + } + } + + void cleanup() { + mBackground = null; + mPunchThroughGraphic = null; + mHandTouchGraphic = null; + mIsInitialized = false; + } + + public String getDrawIdentifier() { + return mDrawIdentifier; + } + + private int[] getPunchThroughPositions() { + if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)) { + return new int[]{getMeasuredWidth() / 2, getMeasuredHeight() - (mButtonBarHeight / 2)}; + } else if (mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)) { + return new int[]{getMeasuredWidth() - (mButtonBarHeight / 2), getMeasuredHeight() / 2}; + } else if (mDrawIdentifier.equals(WORKSPACE_LARGE)) { + final float scale = LauncherApplication.getScreenDensity(); + final int cornerXOffset = (int) (scale * 15); + final int cornerYOffset = (int) (scale * 10); + return new int[]{getMeasuredWidth() - cornerXOffset, cornerYOffset}; + } else if (mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || + mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || + mDrawIdentifier.equals(ALLAPPS_LARGE)) { + return mPositionData; + } + return new int[]{-1, -1}; + } + + @Override + public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) { + if (descendant.includeForAccessibility()) { + return descendant; + } + return null; + } + + @Override + public View focusSearch(int direction) { + return this.focusSearch(null, direction); + } + + @Override + public View focusSearch(View focused, int direction) { + return FocusFinder.getInstance().findNextFocus(this, focused, direction); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) + || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) + || mDrawIdentifier.equals(WORKSPACE_LARGE) + || mDrawIdentifier.equals(ALLAPPS_PORTRAIT) + || mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) + || mDrawIdentifier.equals(ALLAPPS_LARGE) + || mDrawIdentifier.equals(WORKSPACE_CUSTOM)); + } + + @Override + public boolean onTouchEvent(android.view.MotionEvent event) { + if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || + mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || + mDrawIdentifier.equals(WORKSPACE_LARGE) || + mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || + mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || + mDrawIdentifier.equals(ALLAPPS_LARGE)) { + + int[] positions = getPunchThroughPositions(); + for (int i = 0; i < positions.length; i += 2) { + double diff = Math.sqrt(Math.pow(event.getX() - positions[i], 2) + + Math.pow(event.getY() - positions[i + 1], 2)); + if (diff < mRevealRadius) { + return false; + } + } + } else if (mDrawIdentifier.equals(FOLDER_PORTRAIT) || + mDrawIdentifier.equals(FOLDER_LANDSCAPE) || + mDrawIdentifier.equals(FOLDER_LARGE)) { + Folder f = mLauncher.getWorkspace().getOpenFolder(); + if (f != null) { + Rect r = new Rect(); + f.getHitRect(r); + if (r.contains((int) event.getX(), (int) event.getY())) { + return false; + } + } + } + return true; + }; + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mIsInitialized) { + DisplayMetrics metrics = new DisplayMetrics(); + mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics); + + // Initialize the draw buffer (to allow punching through) + Bitmap b = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + + // Draw the background + if (mBackground == null) { + if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) || + mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) || + mDrawIdentifier.equals(WORKSPACE_LARGE)) { + mBackground = getResources().getDrawable(R.drawable.bg_cling1); + } else if (mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || + mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || + mDrawIdentifier.equals(ALLAPPS_LARGE)) { + mBackground = getResources().getDrawable(R.drawable.bg_cling2); + } else if (mDrawIdentifier.equals(FOLDER_PORTRAIT) || + mDrawIdentifier.equals(FOLDER_LANDSCAPE)) { + mBackground = getResources().getDrawable(R.drawable.bg_cling3); + } else if (mDrawIdentifier.equals(FOLDER_LARGE)) { + mBackground = getResources().getDrawable(R.drawable.bg_cling4); + } else if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) { + mBackground = getResources().getDrawable(R.drawable.bg_cling5); + } + } + if (mBackground != null) { + mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); + mBackground.draw(c); + } else { + c.drawColor(0x99000000); + } + + int cx = -1; + int cy = -1; + float scale = mRevealRadius / mPunchThroughGraphicCenterRadius; + int dw = (int) (scale * mPunchThroughGraphic.getIntrinsicWidth()); + int dh = (int) (scale * mPunchThroughGraphic.getIntrinsicHeight()); + + // Determine where to draw the punch through graphic + int[] positions = getPunchThroughPositions(); + for (int i = 0; i < positions.length; i += 2) { + cx = positions[i]; + cy = positions[i + 1]; + if (cx > -1 && cy > -1) { + c.drawCircle(cx, cy, mRevealRadius, mErasePaint); + mPunchThroughGraphic.setBounds(cx - dw/2, cy - dh/2, cx + dw/2, cy + dh/2); + mPunchThroughGraphic.draw(c); + } + } + + // Draw the hand graphic in All Apps + if (mDrawIdentifier.equals(ALLAPPS_PORTRAIT) || + mDrawIdentifier.equals(ALLAPPS_LANDSCAPE) || + mDrawIdentifier.equals(ALLAPPS_LARGE)) { + if (mHandTouchGraphic == null) { + mHandTouchGraphic = getResources().getDrawable(R.drawable.hand); + } + int offset = mAppIconSize / 4; + mHandTouchGraphic.setBounds(cx + offset, cy + offset, + cx + mHandTouchGraphic.getIntrinsicWidth() + offset, + cy + mHandTouchGraphic.getIntrinsicHeight() + offset); + mHandTouchGraphic.draw(c); + } + + canvas.drawBitmap(b, 0, 0, null); + c.setBitmap(null); + b = null; + } + + // Draw the rest of the cling + super.dispatchDraw(canvas); + }; +} diff --git a/src/com/cyanogenmod/trebuchet/DeferredHandler.java b/src/com/cyanogenmod/trebuchet/DeferredHandler.java new file mode 100644 index 000000000..48582f0ec --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DeferredHandler.java @@ -0,0 +1,112 @@ +/* + * 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.cyanogenmod.trebuchet; + +import java.util.LinkedList; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; + +/** + * Queue of things to run on a looper thread. Items posted with {@link #post} will not + * be actually enqued on the handler until after the last one has run, to keep from + * starving the thread. + * + * This class is fifo. + */ +public class DeferredHandler { + private LinkedList mQueue = new LinkedList(); + private MessageQueue mMessageQueue = Looper.myQueue(); + private Impl mHandler = new Impl(); + + private class Impl extends Handler implements MessageQueue.IdleHandler { + public void handleMessage(Message msg) { + Runnable r; + synchronized (mQueue) { + if (mQueue.size() == 0) { + return; + } + r = mQueue.removeFirst(); + } + r.run(); + synchronized (mQueue) { + scheduleNextLocked(); + } + } + + public boolean queueIdle() { + handleMessage(null); + return false; + } + } + + private class IdleRunnable implements Runnable { + Runnable mRunnable; + + IdleRunnable(Runnable r) { + mRunnable = r; + } + + public void run() { + mRunnable.run(); + } + } + + public DeferredHandler() { + } + + /** Schedule runnable to run after everything that's on the queue right now. */ + public void post(Runnable runnable) { + synchronized (mQueue) { + mQueue.add(runnable); + if (mQueue.size() == 1) { + scheduleNextLocked(); + } + } + } + + /** Schedule runnable to run when the queue goes idle. */ + public void postIdle(final Runnable runnable) { + post(new IdleRunnable(runnable)); + } + + public void cancelRunnable(Runnable runnable) { + synchronized (mQueue) { + while (mQueue.remove(runnable)) { } + } + } + + public void cancel() { + synchronized (mQueue) { + mQueue.clear(); + } + } + + void scheduleNextLocked() { + if (mQueue.size() > 0) { + Runnable peek = mQueue.getFirst(); + if (peek instanceof IdleRunnable) { + mMessageQueue.addIdleHandler(mHandler); + } else { + mHandler.sendEmptyMessage(1); + } + } + } +} + diff --git a/src/com/cyanogenmod/trebuchet/DeleteDropTarget.java b/src/com/cyanogenmod/trebuchet/DeleteDropTarget.java new file mode 100644 index 000000000..9576566a1 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DeleteDropTarget.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; + +import com.cyanogenmod.trebuchet.R; + +public class DeleteDropTarget extends ButtonDropTarget { + private static int DELETE_ANIMATION_DURATION = 285; + private static int FLING_DELETE_ANIMATION_DURATION = 350; + private static float FLING_TO_DELETE_FRICTION = 0.035f; + private static int MODE_FLING_DELETE_TO_TRASH = 0; + private static int MODE_FLING_DELETE_ALONG_VECTOR = 1; + + private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR; + + private ColorStateList mOriginalTextColor; + private TransitionDrawable mUninstallDrawable; + private TransitionDrawable mRemoveDrawable; + private TransitionDrawable mCurrentDrawable; + + public DeleteDropTarget(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // Get the drawable + mOriginalTextColor = getTextColors(); + + // Get the hover color + Resources r = getResources(); + mHoverColor = r.getColor(R.color.delete_target_hover_tint); + mUninstallDrawable = (TransitionDrawable) + r.getDrawable(R.drawable.uninstall_target_selector); + mRemoveDrawable = (TransitionDrawable) r.getDrawable(R.drawable.remove_target_selector); + + mRemoveDrawable.setCrossFadeEnabled(true); + mUninstallDrawable.setCrossFadeEnabled(true); + + // The current drawable is set to either the remove drawable or the uninstall drawable + // and is initially set to the remove drawable, as set in the layout xml. + mCurrentDrawable = (TransitionDrawable) getCurrentDrawable(); + + // Remove the text in the Phone UI in landscape + int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + if (!LauncherApplication.isScreenLarge()) { + setText(""); + } + } + } + + private boolean isAllAppsApplication(DragSource source, Object info) { + return (source instanceof AppsCustomizePagedView) && (info instanceof ApplicationInfo); + } + private boolean isAllAppsWidget(DragSource source, Object info) { + if (source instanceof AppsCustomizePagedView) { + if (info instanceof PendingAddItemInfo) { + PendingAddItemInfo addInfo = (PendingAddItemInfo) info; + switch (addInfo.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + return true; + } + } + } + return false; + } + private boolean isDragSourceWorkspaceOrFolder(DragObject d) { + return (d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder); + } + private boolean isWorkspaceOrFolderApplication(DragObject d) { + return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof ShortcutInfo); + } + private boolean isWorkspaceOrFolderWidget(DragObject d) { + return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof LauncherAppWidgetInfo); + } + private boolean isWorkspaceFolder(DragObject d) { + return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo); + } + + private void setHoverColor() { + mCurrentDrawable.startTransition(mTransitionDuration); + setTextColor(mHoverColor); + } + private void resetHoverColor() { + mCurrentDrawable.resetTransition(); + setTextColor(mOriginalTextColor); + } + + @Override + public boolean acceptDrop(DragObject d) { + // We can remove everything including App shortcuts, folders, widgets, etc. + return true; + } + + @Override + public void onDragStart(DragSource source, Object info, int dragAction) { + boolean isVisible = true; + boolean isUninstall = false; + + // If we are dragging a widget from AppsCustomize, hide the delete target + if (isAllAppsWidget(source, info)) { + isVisible = false; + } + + // If we are dragging an application from AppsCustomize, only show the control if we can + // delete the app (it was downloaded), and rename the string to "uninstall" in such a case + if (isAllAppsApplication(source, info)) { + ApplicationInfo appInfo = (ApplicationInfo) info; + if ((appInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) != 0) { + isUninstall = true; + } else { + isVisible = false; + } + } + + if (isUninstall) { + setCompoundDrawablesWithIntrinsicBounds(mUninstallDrawable, null, null, null); + } else { + setCompoundDrawablesWithIntrinsicBounds(mRemoveDrawable, null, null, null); + } + mCurrentDrawable = (TransitionDrawable) getCurrentDrawable(); + + mActive = isVisible; + resetHoverColor(); + ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE); + if (getText().length() > 0) { + setText(isUninstall ? R.string.delete_target_uninstall_label + : R.string.delete_target_label); + } + } + + @Override + public void onDragEnd() { + super.onDragEnd(); + mActive = false; + } + + public void onDragEnter(DragObject d) { + super.onDragEnter(d); + + setHoverColor(); + } + + public void onDragExit(DragObject d) { + super.onDragExit(d); + + if (!d.dragComplete) { + resetHoverColor(); + } else { + // Restore the hover color if we are deleting + d.dragView.setColor(mHoverColor); + } + } + + private void animateToTrashAndCompleteDrop(final DragObject d) { + DragLayer dragLayer = mLauncher.getDragLayer(); + Rect from = new Rect(); + dragLayer.getViewRectRelativeToSelf(d.dragView, from); + Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), + mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight()); + float scale = (float) to.width() / from.width(); + + mSearchDropTargetBar.deferOnDragEnd(); + Runnable onAnimationEndRunnable = new Runnable() { + @Override + public void run() { + mSearchDropTargetBar.onDragEnd(); + mLauncher.exitSpringLoadedDragMode(); + completeDrop(d); + } + }; + dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f, + DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2), + new LinearInterpolator(), onAnimationEndRunnable, + DragLayer.ANIMATION_END_DISAPPEAR, null); + } + + private void completeDrop(DragObject d) { + ItemInfo item = (ItemInfo) d.dragInfo; + + if (isAllAppsApplication(d.dragSource, item)) { + // Uninstall the application if it is being dragged from AppsCustomize + mLauncher.startApplicationUninstallActivity((ApplicationInfo) item); + } else if (isWorkspaceOrFolderApplication(d)) { + LauncherModel.deleteItemFromDatabase(mLauncher, item); + } else if (isWorkspaceFolder(d)) { + // Remove the folder from the workspace and delete the contents from launcher model + FolderInfo folderInfo = (FolderInfo) item; + mLauncher.removeFolder(folderInfo); + LauncherModel.deleteFolderContentsFromDatabase(mLauncher, folderInfo); + } else if (isWorkspaceOrFolderWidget(d)) { + // Remove the widget from the workspace + mLauncher.removeAppWidget((LauncherAppWidgetInfo) item); + LauncherModel.deleteItemFromDatabase(mLauncher, item); + + final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item; + final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost(); + if (appWidgetHost != null) { + // Deleting an app widget ID is a void call but writes to disk before returning + // to the caller... + new Thread("deleteAppWidgetId") { + public void run() { + appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId); + } + }.start(); + } + } + } + + public void onDrop(DragObject d) { + animateToTrashAndCompleteDrop(d); + } + + /** + * Creates an animation from the current drag view to the delete trash icon. + */ + private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer, + DragObject d, PointF vel, ViewConfiguration config) { + final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), + mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight()); + final Rect from = new Rect(); + dragLayer.getViewRectRelativeToSelf(d.dragView, from); + + // Calculate how far along the velocity vector we should put the intermediate point on + // the bezier curve + float velocity = Math.abs(vel.length()); + float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f)); + int offsetY = (int) (-from.top * vp); + int offsetX = (int) (offsetY / (vel.y / vel.x)); + final float y2 = from.top + offsetY; // intermediate t/l + final float x2 = from.left + offsetX; + final float x1 = from.left; // drag view t/l + final float y1 = from.top; + final float x3 = to.left; // delete target t/l + final float y3 = to.top; + + final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() { + @Override + public float getInterpolation(float t) { + return t * t * t * t * t * t * t * t; + } + }; + return new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final DragView dragView = (DragView) dragLayer.getAnimatedView(); + float t = ((Float) animation.getAnimatedValue()).floatValue(); + float tp = scaleAlphaInterpolator.getInterpolation(t); + float initialScale = dragView.getInitialScale(); + float finalAlpha = 0.5f; + float scale = dragView.getScaleX(); + float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f; + float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f; + float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) + + (t * t) * x3; + float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) + + (t * t) * y3; + + dragView.setTranslationX(x); + dragView.setTranslationY(y); + dragView.setScaleX(initialScale * (1f - tp)); + dragView.setScaleY(initialScale * (1f - tp)); + dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp)); + } + }; + } + + /** + * Creates an animation from the current drag view along its current velocity vector. + * For this animation, the alpha runs for a fixed duration and we update the position + * progressively. + */ + private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener { + private DragLayer mDragLayer; + private PointF mVelocity; + private Rect mFrom; + private long mPrevTime; + private boolean mHasOffsetForScale; + private float mFriction; + + private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f); + + public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from, + long startTime, float friction) { + mDragLayer = dragLayer; + mVelocity = vel; + mFrom = from; + mPrevTime = startTime; + mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction); + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final DragView dragView = (DragView) mDragLayer.getAnimatedView(); + float t = ((Float) animation.getAnimatedValue()).floatValue(); + long curTime = AnimationUtils.currentAnimationTimeMillis(); + + if (!mHasOffsetForScale) { + mHasOffsetForScale = true; + float scale = dragView.getScaleX(); + float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f; + float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f; + + mFrom.left += xOffset; + mFrom.top += yOffset; + } + + mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f); + mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f); + + dragView.setTranslationX(mFrom.left); + dragView.setTranslationY(mFrom.top); + dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t)); + + mVelocity.x *= mFriction; + mVelocity.y *= mFriction; + mPrevTime = curTime; + } + }; + private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer, + DragObject d, PointF vel, final long startTime, final int duration, + ViewConfiguration config) { + final Rect from = new Rect(); + dragLayer.getViewRectRelativeToSelf(d.dragView, from); + + return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime, + FLING_TO_DELETE_FRICTION); + } + + public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) { + final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView; + + // Don't highlight the icon as it's animating + d.dragView.setColor(0); + d.dragView.updateInitialScaleToCurrentScale(); + // Don't highlight the target if we are flinging from AllApps + if (isAllApps) { + resetHoverColor(); + } + + if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) { + // Defer animating out the drop target if we are animating to it + mSearchDropTargetBar.deferOnDragEnd(); + mSearchDropTargetBar.finishAnimations(); + } + + final ViewConfiguration config = ViewConfiguration.get(mLauncher); + final DragLayer dragLayer = mLauncher.getDragLayer(); + final int duration = FLING_DELETE_ANIMATION_DURATION; + final long startTime = AnimationUtils.currentAnimationTimeMillis(); + + // NOTE: Because it takes time for the first frame of animation to actually be + // called and we expect the animation to be a continuation of the fling, we have + // to account for the time that has elapsed since the fling finished. And since + // we don't have a startDelay, we will always get call to update when we call + // start() (which we want to ignore). + final TimeInterpolator tInterpolator = new TimeInterpolator() { + private int mCount = -1; + private float mOffset = 0f; + + @Override + public float getInterpolation(float t) { + if (mCount < 0) { + mCount++; + } else if (mCount == 0) { + mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() - + startTime) / duration); + mCount++; + } + return Math.min(1f, mOffset + t); + } + }; + AnimatorUpdateListener updateCb = null; + if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) { + updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config); + } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) { + updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime, + duration, config); + } + Runnable onAnimationEndRunnable = new Runnable() { + @Override + public void run() { + mSearchDropTargetBar.onDragEnd(); + + // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up + // itself, otherwise, complete the drop to initiate the deletion process + if (!isAllApps) { + mLauncher.exitSpringLoadedDragMode(); + completeDrop(d); + } + mLauncher.getDragController().onDeferredEndFling(d); + } + }; + dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable, + DragLayer.ANIMATION_END_DISAPPEAR, null); + } +} diff --git a/src/com/cyanogenmod/trebuchet/DragController.java b/src/com/cyanogenmod/trebuchet/DragController.java new file mode 100644 index 000000000..cdb5060f9 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DragController.java @@ -0,0 +1,809 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Handler; +import android.os.IBinder; +import android.os.Vibrator; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.inputmethod.InputMethodManager; + +import com.cyanogenmod.trebuchet.R; + +import java.util.ArrayList; + +/** + * Class for initiating a drag within a view or across multiple views. + */ +public class DragController { + private static final String TAG = "Launcher.DragController"; + + /** Indicates the drag is a move. */ + public static int DRAG_ACTION_MOVE = 0; + + /** Indicates the drag is a copy. */ + public static int DRAG_ACTION_COPY = 1; + + private static final int SCROLL_DELAY = 500; + private static final int RESCROLL_DELAY = 750; + private static final int VIBRATE_DURATION = 15; + + private static final boolean PROFILE_DRAWING_DURING_DRAG = false; + + private static final int SCROLL_OUTSIDE_ZONE = 0; + private static final int SCROLL_WAITING_IN_ZONE = 1; + + static final int SCROLL_NONE = -1; + static final int SCROLL_LEFT = 0; + static final int SCROLL_RIGHT = 1; + + private static final float MAX_FLING_DEGREES = 35f; + + private Launcher mLauncher; + private Handler mHandler; + private final Vibrator mVibrator; + + // temporaries to avoid gc thrash + private Rect mRectTemp = new Rect(); + private final int[] mCoordinatesTemp = new int[2]; + + /** Whether or not we're dragging. */ + private boolean mDragging; + + /** X coordinate of the down event. */ + private int mMotionDownX; + + /** Y coordinate of the down event. */ + private int mMotionDownY; + + /** the area at the edge of the screen that makes the workspace go left + * or right while you're dragging. + */ + private int mScrollZone; + + private DropTarget.DragObject mDragObject; + + /** Who can receive drop events */ + private ArrayList mDropTargets = new ArrayList(); + private ArrayList mListeners = new ArrayList(); + private DropTarget mFlingToDeleteDropTarget; + + /** The window token used as the parent for the DragView. */ + private IBinder mWindowToken; + + /** The view that will be scrolled when dragging to the left and right edges of the screen. */ + private View mScrollView; + + private View mMoveTarget; + + private DragScroller mDragScroller; + private int mScrollState = SCROLL_OUTSIDE_ZONE; + private ScrollRunnable mScrollRunnable = new ScrollRunnable(); + + private DropTarget mLastDropTarget; + + private InputMethodManager mInputMethodManager; + + private int mLastTouch[] = new int[2]; + private long mLastTouchUpTime = -1; + private int mDistanceSinceScroll = 0; + + private int mTmpPoint[] = new int[2]; + private Rect mDragLayerRect = new Rect(); + + protected int mFlingToDeleteThresholdVelocity; + private VelocityTracker mVelocityTracker; + + /** + * Interface to receive notifications when a drag starts or stops + */ + interface DragListener { + + /** + * A drag has begun + * + * @param source An object representing where the drag originated + * @param info The data associated with the object that is being dragged + * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} + * or {@link DragController#DRAG_ACTION_COPY} + */ + void onDragStart(DragSource source, Object info, int dragAction); + + /** + * The drag has ended + */ + void onDragEnd(); + } + + /** + * Used to create a new DragLayer from XML. + * + * @param context The application's context. + */ + public DragController(Launcher launcher) { + Resources r = launcher.getResources(); + mLauncher = launcher; + mHandler = new Handler(); + mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone); + mVelocityTracker = VelocityTracker.obtain(); + mVibrator = (Vibrator) launcher.getSystemService(Context.VIBRATOR_SERVICE); + + float density = r.getDisplayMetrics().density; + mFlingToDeleteThresholdVelocity = + (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density); + } + + public boolean dragging() { + return mDragging; + } + + /** + * 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, float initialDragViewScale) { + int[] loc = mCoordinatesTemp; + mLauncher.getDragLayer().getLocationInDragLayer(v, loc); + int dragLayerX = loc[0] + v.getPaddingLeft() + + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); + int dragLayerY = loc[1] + v.getPaddingTop() + + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); + + startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, + initialDragViewScale); + + 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 dragLayerX The x position in the DragLayer of the left-top of the bitmap. + * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. + * @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 dragLayerX, int dragLayerY, + DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, + float initialDragViewScale) { + if (PROFILE_DRAWING_DURING_DRAG) { + android.os.Debug.startMethodTracing("Launcher"); + } + + // Hide soft keyboard, if visible + if (mInputMethodManager == null) { + mInputMethodManager = (InputMethodManager) + mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); + } + mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); + + for (DragListener listener : mListeners) { + listener.onDragStart(source, dragInfo, dragAction); + } + + final int registrationX = mMotionDownX - dragLayerX; + final int registrationY = mMotionDownY - dragLayerY; + + final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; + final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; + + mDragging = true; + + mDragObject = new DropTarget.DragObject(); + + mDragObject.dragComplete = false; + mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); + mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); + mDragObject.dragSource = source; + mDragObject.dragInfo = dragInfo; + + mVibrator.vibrate(VIBRATE_DURATION); + + final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, + registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); + + if (dragOffset != null) { + dragView.setDragVisualizeOffset(new Point(dragOffset)); + } + if (dragRegion != null) { + dragView.setDragRegion(new Rect(dragRegion)); + } + + dragView.show(mMotionDownX, mMotionDownY); + handleMoveEvent(mMotionDownX, mMotionDownY); + } + + /** + * Draw the view into a bitmap. + */ + Bitmap getViewBitmap(View v) { + v.clearFocus(); + v.setPressed(false); + + boolean willNotCache = v.willNotCacheDrawing(); + v.setWillNotCacheDrawing(false); + + // Reset the drawing cache background color to fully transparent + // for the duration of this operation + int color = v.getDrawingCacheBackgroundColor(); + v.setDrawingCacheBackgroundColor(0); + float alpha = v.getAlpha(); + v.setAlpha(1.0f); + + if (color != 0) { + v.destroyDrawingCache(); + } + v.buildDrawingCache(); + Bitmap cacheBitmap = v.getDrawingCache(); + if (cacheBitmap == null) { + Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); + return null; + } + + Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); + + // Restore the view + v.destroyDrawingCache(); + v.setAlpha(alpha); + v.setWillNotCacheDrawing(willNotCache); + v.setDrawingCacheBackgroundColor(color); + + return bitmap; + } + + /** + * Call this from a drag source view like this: + * + *
+     *  @Override
+     *  public boolean dispatchKeyEvent(KeyEvent event) {
+     *      return mDragController.dispatchKeyEvent(this, event)
+     *              || super.dispatchKeyEvent(event);
+     * 
+ */ + public boolean dispatchKeyEvent(KeyEvent event) { + return mDragging; + } + + public boolean isDragging() { + return mDragging; + } + + /** + * Stop dragging without dropping. + */ + public void cancelDrag() { + if (mDragging) { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + } + mDragObject.deferDragViewCleanupPostAnimation = false; + mDragObject.cancelled = true; + mDragObject.dragComplete = true; + mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); + } + endDrag(); + } + public void onAppsRemoved(ArrayList apps, Context context) { + // Cancel the current drag if we are removing an app that we are dragging + if (mDragObject != null) { + Object rawDragInfo = mDragObject.dragInfo; + if (rawDragInfo instanceof ShortcutInfo) { + ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; + for (ApplicationInfo info : apps) { + // Added null checks to prevent NPE we've seen in the wild + if (dragInfo != null && + dragInfo.intent != null && + info.intent != null) { + boolean isSamePackage = dragInfo.getPackageName().equals( + info.getPackageName()); + if (isSamePackage) { + cancelDrag(); + return; + } + } + } + } + } + } + + private void endDrag() { + if (mDragging) { + mDragging = false; + clearScrollRunnable(); + boolean isDeferred = false; + if (mDragObject.dragView != null) { + isDeferred = mDragObject.deferDragViewCleanupPostAnimation; + if (!isDeferred) { + mDragObject.dragView.remove(); + } + mDragObject.dragView = null; + } + + // Only end the drag if we are not deferred + if (!isDeferred) { + for (DragListener listener : mListeners) { + listener.onDragEnd(); + } + } + } + + releaseVelocityTracker(); + } + + /** + * This only gets called as a result of drag view cleanup being deferred in endDrag(); + */ + void onDeferredEndDrag(DragView dragView) { + dragView.remove(); + + // If we skipped calling onDragEnd() before, do it now + for (DragListener listener : mListeners) { + listener.onDragEnd(); + } + } + + void onDeferredEndFling(DropTarget.DragObject d) { + d.dragSource.onFlingToDeleteCompleted(); + } + + /** + * Clamps the position to the drag layer bounds. + */ + private int[] getClampedDragLayerPos(float x, float y) { + mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); + mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); + mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); + return mTmpPoint; + } + + long getLastGestureUpTime() { + if (mDragging) { + return System.currentTimeMillis(); + } else { + return mLastTouchUpTime; + } + } + + void resetLastGestureUpTime() { + mLastTouchUpTime = -1; + } + + /** + * Call this from a drag source view. + */ + public boolean onInterceptTouchEvent(MotionEvent ev) { + @SuppressWarnings("all") // suppress dead code warning + final boolean debug = false; + if (debug) { + Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" + + mDragging); + } + + // Update the velocity tracker + acquireVelocityTrackerAndAddMovement(ev); + + final int action = ev.getAction(); + final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); + final int dragLayerX = dragLayerPos[0]; + final int dragLayerY = dragLayerPos[1]; + + switch (action) { + case MotionEvent.ACTION_MOVE: + break; + case MotionEvent.ACTION_DOWN: + // Remember location of down touch + mMotionDownX = dragLayerX; + mMotionDownY = dragLayerY; + mLastDropTarget = null; + break; + case MotionEvent.ACTION_UP: + mLastTouchUpTime = System.currentTimeMillis(); + if (mDragging) { + PointF vec = isFlingingToDelete(mDragObject.dragSource); + if (vec != null) { + dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); + } else { + drop(dragLayerX, dragLayerY); + } + } + endDrag(); + break; + case MotionEvent.ACTION_CANCEL: + cancelDrag(); + break; + } + + return mDragging; + } + + /** + * Sets the view that should handle move events. + */ + void setMoveTarget(View view) { + mMoveTarget = view; + } + + public boolean dispatchUnhandledMove(View focused, int direction) { + return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); + } + + private void clearScrollRunnable() { + mHandler.removeCallbacks(mScrollRunnable); + if (mScrollState == SCROLL_WAITING_IN_ZONE) { + mScrollState = SCROLL_OUTSIDE_ZONE; + mScrollRunnable.setDirection(SCROLL_RIGHT); + mDragScroller.onExitScrollArea(); + mLauncher.getDragLayer().onExitScrollArea(); + } + } + + private void handleMoveEvent(int x, int y) { + mDragObject.dragView.move(x, y); + + // Drop on someone? + final int[] coordinates = mCoordinatesTemp; + DropTarget dropTarget = findDropTarget(x, y, coordinates); + mDragObject.x = coordinates[0]; + mDragObject.y = coordinates[1]; + if (dropTarget != null) { + DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); + if (delegate != null) { + dropTarget = delegate; + } + + if (mLastDropTarget != dropTarget) { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + } + dropTarget.onDragEnter(mDragObject); + } + dropTarget.onDragOver(mDragObject); + } else { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + } + } + mLastDropTarget = dropTarget; + + // 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(mLauncher).getScaledWindowTouchSlop(); + mDistanceSinceScroll += + Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); + mLastTouch[0] = x; + mLastTouch[1] = y; + final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; + + if (x < mScrollZone) { + if (mScrollState == SCROLL_OUTSIDE_ZONE) { + mScrollState = SCROLL_WAITING_IN_ZONE; + if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) { + mLauncher.getDragLayer().onEnterScrollArea(SCROLL_LEFT); + mScrollRunnable.setDirection(SCROLL_LEFT); + mHandler.postDelayed(mScrollRunnable, delay); + } + } + } else if (x > mScrollView.getWidth() - mScrollZone) { + if (mScrollState == SCROLL_OUTSIDE_ZONE) { + mScrollState = SCROLL_WAITING_IN_ZONE; + if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) { + mLauncher.getDragLayer().onEnterScrollArea(SCROLL_RIGHT); + mScrollRunnable.setDirection(SCROLL_RIGHT); + mHandler.postDelayed(mScrollRunnable, delay); + } + } + } else { + clearScrollRunnable(); + } + } + + public void forceMoveEvent() { + if (mDragging) { + handleMoveEvent(mDragObject.x, mDragObject.y); + } + } + + /** + * Call this from a drag source view. + */ + public boolean onTouchEvent(MotionEvent ev) { + if (!mDragging) { + return false; + } + + // Update the velocity tracker + acquireVelocityTrackerAndAddMovement(ev); + + final int action = ev.getAction(); + final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); + final int dragLayerX = dragLayerPos[0]; + final int dragLayerY = dragLayerPos[1]; + + switch (action) { + case MotionEvent.ACTION_DOWN: + // Remember where the motion event started + mMotionDownX = dragLayerX; + mMotionDownY = dragLayerY; + + if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { + mScrollState = SCROLL_WAITING_IN_ZONE; + mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); + } else { + mScrollState = SCROLL_OUTSIDE_ZONE; + } + break; + case MotionEvent.ACTION_MOVE: + handleMoveEvent(dragLayerX, dragLayerY); + break; + case MotionEvent.ACTION_UP: + // Ensure that we've processed a move event at the current pointer location. + handleMoveEvent(dragLayerX, dragLayerY); + mHandler.removeCallbacks(mScrollRunnable); + + if (mDragging) { + PointF vec = isFlingingToDelete(mDragObject.dragSource); + if (vec != null) { + dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); + } else { + drop(dragLayerX, dragLayerY); + } + } + endDrag(); + break; + case MotionEvent.ACTION_CANCEL: + mHandler.removeCallbacks(mScrollRunnable); + cancelDrag(); + break; + } + + return true; + } + + /** + * Determines whether the user flung the current item to delete it. + * + * @return the vector at which the item was flung, or null if no fling was detected. + */ + private PointF isFlingingToDelete(DragSource source) { + if (mFlingToDeleteDropTarget == null) return null; + if (!source.supportsFlingToDelete()) return null; + + ViewConfiguration config = ViewConfiguration.get(mLauncher); + mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); + + if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { + // Do a quick dot product test to ensure that we are flinging upwards + PointF vel = new PointF(mVelocityTracker.getXVelocity(), + mVelocityTracker.getYVelocity()); + PointF upVec = new PointF(0f, -1f); + float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / + (vel.length() * upVec.length())); + if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { + return vel; + } + } + return null; + } + + private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { + final int[] coordinates = mCoordinatesTemp; + + mDragObject.x = coordinates[0]; + mDragObject.y = coordinates[1]; + + // Clean up dragging on the target if it's not the current fling delete target otherwise, + // start dragging to it. + if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { + mLastDropTarget.onDragExit(mDragObject); + } + + // Drop onto the fling-to-delete target + boolean accepted = false; + mFlingToDeleteDropTarget.onDragEnter(mDragObject); + // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for + // "drop" + mDragObject.dragComplete = true; + mFlingToDeleteDropTarget.onDragExit(mDragObject); + if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { + mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y, + vel); + accepted = true; + } + mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, + accepted); + } + + private void drop(float x, float y) { + final int[] coordinates = mCoordinatesTemp; + final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); + + mDragObject.x = coordinates[0]; + mDragObject.y = coordinates[1]; + boolean accepted = false; + if (dropTarget != null) { + mDragObject.dragComplete = true; + dropTarget.onDragExit(mDragObject); + if (dropTarget.acceptDrop(mDragObject)) { + dropTarget.onDrop(mDragObject); + accepted = true; + } + } + mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted); + } + + private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { + final Rect r = mRectTemp; + + final ArrayList dropTargets = mDropTargets; + final int count = dropTargets.size(); + for (int i=count-1; i>=0; i--) { + DropTarget target = dropTargets.get(i); + if (!target.isDropEnabled()) + continue; + + target.getHitRect(r); + + // Convert the hit rect to DragLayer coordinates + target.getLocationInDragLayer(dropCoordinates); + r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); + + mDragObject.x = x; + mDragObject.y = y; + if (r.contains(x, y)) { + DropTarget delegate = target.getDropTargetDelegate(mDragObject); + if (delegate != null) { + target = delegate; + target.getLocationInDragLayer(dropCoordinates); + } + + // Make dropCoordinates relative to the DropTarget + dropCoordinates[0] = x - dropCoordinates[0]; + dropCoordinates[1] = y - dropCoordinates[1]; + + return target; + } + } + return null; + } + + public void setDragScoller(DragScroller scroller) { + mDragScroller = scroller; + } + + public void setWindowToken(IBinder token) { + mWindowToken = token; + } + + /** + * Sets the drag listner which will be notified when a drag starts or ends. + */ + public void addDragListener(DragListener l) { + mListeners.add(l); + } + + /** + * Remove a previously installed drag listener. + */ + public void removeDragListener(DragListener l) { + mListeners.remove(l); + } + + /** + * Add a DropTarget to the list of potential places to receive drop events. + */ + public void addDropTarget(DropTarget target) { + mDropTargets.add(target); + } + + /** + * Don't send drop events to target any more. + */ + public void removeDropTarget(DropTarget target) { + mDropTargets.remove(target); + } + + /** + * Sets the current fling-to-delete drop target. + */ + public void setFlingToDeleteDropTarget(DropTarget target) { + mFlingToDeleteDropTarget = target; + } + + private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + } + + private void releaseVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + /** + * Set which view scrolls for touch events near the edge of the screen. + */ + public void setScrollView(View v) { + mScrollView = v; + } + + DragView getDragView() { + return mDragObject.dragView; + } + + private class ScrollRunnable implements Runnable { + private int mDirection; + + ScrollRunnable() { + } + + public void run() { + if (mDragScroller != null) { + if (mDirection == SCROLL_LEFT) { + mDragScroller.scrollLeft(); + } else { + mDragScroller.scrollRight(); + } + mScrollState = SCROLL_OUTSIDE_ZONE; + mDistanceSinceScroll = 0; + mDragScroller.onExitScrollArea(); + mLauncher.getDragLayer().onExitScrollArea(); + + if (isDragging()) { + // Force an update so that we can requeue the scroller if necessary + forceMoveEvent(); + } + } + } + + void setDirection(int direction) { + mDirection = direction; + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/DragLayer.java b/src/com/cyanogenmod/trebuchet/DragLayer.java new file mode 100644 index 000000000..76b74e9c5 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DragLayer.java @@ -0,0 +1,766 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; + +import java.util.ArrayList; + +/** + * A ViewGroup that coordinates dragging across its descendants + */ +public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener { + private DragController mDragController; + private int[] mTmpXY = new int[2]; + + private int mXDown, mYDown; + private Launcher mLauncher; + + // Variables relating to resizing widgets + private final ArrayList mResizeFrames = + new ArrayList(); + private AppWidgetResizeFrame mCurrentResizeFrame; + + // Variables relating to animation of views after drop + private ValueAnimator mDropAnim = null; + private ValueAnimator mFadeOutAnim = null; + private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); + private DragView mDropView = null; + private int mAnchorViewInitialScrollX = 0; + private View mAnchorView = null; + + private boolean mHoverPointClosesFolder = false; + private Rect mHitRect = new Rect(); + private int mWorkspaceIndex = -1; + private int mQsbIndex = -1; + public static final int ANIMATION_END_DISAPPEAR = 0; + public static final int ANIMATION_END_FADE_OUT = 1; + public static final int ANIMATION_END_REMAIN_VISIBLE = 2; + + /** + * Used to create a new DragLayer from XML. + * + * @param context The application's context. + * @param attrs The attributes set containing the Workspace's customization values. + */ + public DragLayer(Context context, AttributeSet attrs) { + super(context, attrs); + + // Disable multitouch across the workspace/all apps/customize tray + setMotionEventSplittingEnabled(false); + setChildrenDrawingOrderEnabled(true); + setOnHierarchyChangeListener(this); + + mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo); + mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo); + } + + public void setup(Launcher launcher, DragController controller) { + mLauncher = launcher; + mDragController = controller; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); + } + + private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { + getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect); + if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { + return true; + } + return false; + } + + private boolean isEventOverFolder(Folder folder, MotionEvent ev) { + getDescendantRectRelativeToSelf(folder, mHitRect); + if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { + return true; + } + return false; + } + + private boolean handleTouchDown(MotionEvent ev, boolean intercept) { + Rect hitRect = new Rect(); + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + for (AppWidgetResizeFrame child: mResizeFrames) { + child.getHitRect(hitRect); + if (hitRect.contains(x, y)) { + if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { + mCurrentResizeFrame = child; + mXDown = x; + mYDown = y; + requestDisallowInterceptTouchEvent(true); + return true; + } + } + } + + Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); + if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) { + if (currentFolder.isEditingName()) { + if (!isEventOverFolderTextRegion(currentFolder, ev)) { + currentFolder.dismissEditingName(); + return true; + } + } + + getDescendantRectRelativeToSelf(currentFolder, hitRect); + if (!isEventOverFolder(currentFolder, ev)) { + mLauncher.closeFolder(); + return true; + } + } + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if (handleTouchDown(ev, true)) { + return true; + } + } + clearAllResizeFrames(); + return mDragController.onInterceptTouchEvent(ev); + } + + @Override + public boolean onInterceptHoverEvent(MotionEvent ev) { + Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); + if (currentFolder == null) { + return false; + } else { + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isTouchExplorationEnabled()) { + final int action = ev.getAction(); + boolean isOverFolder; + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + isOverFolder = isEventOverFolder(currentFolder, ev); + if (!isOverFolder) { + sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); + mHoverPointClosesFolder = true; + return true; + } else if (isOverFolder) { + mHoverPointClosesFolder = false; + } else { + return true; + } + case MotionEvent.ACTION_HOVER_MOVE: + isOverFolder = isEventOverFolder(currentFolder, ev); + if (!isOverFolder && !mHoverPointClosesFolder) { + sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); + mHoverPointClosesFolder = true; + return true; + } else if (isOverFolder) { + mHoverPointClosesFolder = false; + } else { + return true; + } + } + } + } + return false; + } + + private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_FOCUSED); + onInitializeAccessibilityEvent(event); + event.getText().add(getContext().getString(stringId)); + accessibilityManager.sendAccessibilityEvent(event); + } + } + + @Override + public boolean onHoverEvent(MotionEvent ev) { + // If we've received this, we've already done the necessary handling + // in onInterceptHoverEvent. Return true to consume the event. + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + boolean handled = false; + int action = ev.getAction(); + + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if (handleTouchDown(ev, false)) { + return true; + } + } + } + + if (mCurrentResizeFrame != null) { + handled = true; + switch (action) { + case MotionEvent.ACTION_MOVE: + mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); + mCurrentResizeFrame.onTouchUp(); + mCurrentResizeFrame = null; + } + } + if (handled) return true; + return mDragController.onTouchEvent(ev); + } + + /** + * Determine the rect of the descendant in this DragLayer's coordinates + * + * @param descendant The descendant whose coordinates we want to find. + * @param r The rect into which to place the results. + * @return The factor by which this descendant is scaled relative to this DragLayer. + */ + public float getDescendantRectRelativeToSelf(View descendant, Rect r) { + mTmpXY[0] = 0; + mTmpXY[1] = 0; + float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); + r.set(mTmpXY[0], mTmpXY[1], + mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight()); + return scale; + } + + public void getLocationInDragLayer(View child, int[] loc) { + loc[0] = 0; + loc[1] = 0; + getDescendantCoordRelativeToSelf(child, loc); + } + + /** + * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's + * coordinates. + * + * @param descendant The descendant to which the passed coordinate is relative. + * @param coord The coordinate that we want mapped. + * @return The factor by which this descendant is scaled relative to this DragLayer. + */ + public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { + float scale = 1.0f; + float[] pt = {coord[0], coord[1]}; + descendant.getMatrix().mapPoints(pt); + scale *= descendant.getScaleX(); + pt[0] += descendant.getLeft(); + pt[1] += descendant.getTop(); + ViewParent viewParent = descendant.getParent(); + while (viewParent instanceof View && viewParent != this) { + final View view = (View)viewParent; + view.getMatrix().mapPoints(pt); + scale *= view.getScaleX(); + pt[0] += view.getLeft() - view.getScrollX(); + pt[1] += view.getTop() - view.getScrollY(); + viewParent = view.getParent(); + } + coord[0] = (int) Math.round(pt[0]); + coord[1] = (int) Math.round(pt[1]); + return scale; + } + + public void getViewRectRelativeToSelf(View v, Rect r) { + int[] loc = new int[2]; + getLocationInWindow(loc); + int x = loc[0]; + int y = loc[1]; + + v.getLocationInWindow(loc); + int vX = loc[0]; + int vY = loc[1]; + + int left = vX - x; + int top = vY - y; + r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + return mDragController.dispatchUnhandledMove(focused, direction); + } + + public static class LayoutParams extends FrameLayout.LayoutParams { + public int x, y; + public boolean customPosition = false; + + /** + * {@inheritDoc} + */ + public LayoutParams(int width, int height) { + super(width, height); + } + + public void setWidth(int width) { + this.width = width; + } + + public int getWidth() { + return width; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getHeight() { + return height; + } + + public void setX(int x) { + this.x = x; + } + + public int getX() { + return x; + } + + public void setY(int y) { + this.y = y; + } + + public int getY() { + return y; + } + } + + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); + if (flp instanceof LayoutParams) { + final LayoutParams lp = (LayoutParams) flp; + if (lp.customPosition) { + child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); + } + } + } + } + + public void clearAllResizeFrames() { + if (mResizeFrames.size() > 0) { + for (AppWidgetResizeFrame frame: mResizeFrames) { + frame.commitResize(); + removeView(frame); + } + mResizeFrames.clear(); + } + } + + public boolean hasResizeFrames() { + return mResizeFrames.size() > 0; + } + + public boolean isWidgetBeingResized() { + return mCurrentResizeFrame != null; + } + + public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, + CellLayout cellLayout) { + AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), + widget, cellLayout, this); + + LayoutParams lp = new LayoutParams(-1, -1); + lp.customPosition = true; + + addView(resizeFrame, lp); + mResizeFrames.add(resizeFrame); + + resizeFrame.snapToWidget(false); + } + + public void animateViewIntoPosition(DragView dragView, final View child) { + animateViewIntoPosition(dragView, child, null); + } + + public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, + float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, + int duration) { + Rect r = new Rect(); + getViewRectRelativeToSelf(dragView, r); + final int fromX = r.left; + final int fromY = r.top; + + animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY, + onFinishRunnable, animationEndStyle, duration, null); + } + + public void animateViewIntoPosition(DragView dragView, final View child, + final Runnable onFinishAnimationRunnable) { + animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null); + } + + public void animateViewIntoPosition(DragView dragView, final View child, int duration, + final Runnable onFinishAnimationRunnable, View anchorView) { + ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); + CellLayout parent = (CellLayout) (CellLayout) parentChildren.getParent(); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + parentChildren.measureChild(child); + + Rect r = new Rect(); + getViewRectRelativeToSelf(dragView, r); + + int coord[] = new int[2]; + coord[0] = lp.x; + coord[1] = lp.y; + + // Since the child hasn't necessarily been laid out, we force the lp to be updated with + // the correct coordinates (above) and use these to determine the final location + float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); + int toX = coord[0]; + int toY = coord[1]; + if (child instanceof TextView) { + TextView tv = (TextView) child; + + // The child may be scaled (always about the center of the view) so to account for it, + // we have to offset the position by the scaled size. Once we do that, we can center + // the drag view about the scaled child view. + toY += Math.round(scale * tv.getPaddingTop()); + toY -= dragView.getMeasuredHeight() * (1 - scale) / 2; + toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; + } else if (child instanceof FolderIcon) { + // Account for holographic blur padding on the drag view + toY -= Workspace.DRAG_BITMAP_PADDING / 2; + // Center in the x coordinate about the target's drawable + toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; + } else { + toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; + toX -= (Math.round(scale * (dragView.getMeasuredWidth() + - child.getMeasuredWidth()))) / 2; + } + + final int fromX = r.left; + final int fromY = r.top; + child.setVisibility(INVISIBLE); + Runnable onCompleteRunnable = new Runnable() { + public void run() { + child.setVisibility(VISIBLE); + if (onFinishAnimationRunnable != null) { + onFinishAnimationRunnable.run(); + } + } + }; + animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale, + onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); + } + + public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, + final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, + float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, + int animationEndStyle, int duration, View anchorView) { + Rect from = new Rect(fromX, fromY, fromX + + view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); + Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); + animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, + null, null, onCompleteRunnable, animationEndStyle, anchorView); + } + + /** + * This method animates a view at the end of a drag and drop animation. + * + * @param view The view to be animated. This view is drawn directly into DragLayer, and so + * doesn't need to be a child of DragLayer. + * @param from The initial location of the view. Only the left and top parameters are used. + * @param to The final location of the view. Only the left and top parameters are used. This + * location doesn't account for scaling, and so should be centered about the desired + * final location (including scaling). + * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. + * @param finalScale The final scale of the view. The view is scaled about its center. + * @param duration The duration of the animation. + * @param motionInterpolator The interpolator to use for the location of the view. + * @param alphaInterpolator The interpolator to use for the alpha of the view. + * @param onCompleteRunnable Optional runnable to run on animation completion. + * @param fadeOut Whether or not to fade out the view once the animation completes. If true, + * the runnable will execute after the view is faded out. + * @param anchorView If not null, this represents the view which the animated view stays + * anchored to in case scrolling is currently taking place. Note: currently this is + * only used for the X dimension for the case of the workspace. + */ + public void animateView(final DragView view, final Rect from, final Rect to, + final float finalAlpha, final float initScaleX, final float initScaleY, + final float finalScaleX, final float finalScaleY, int duration, + final Interpolator motionInterpolator, final Interpolator alphaInterpolator, + final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { + + // Calculate the duration of the animation based on the object's distance + final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) + + Math.pow(to.top - from.top, 2)); + final Resources res = getResources(); + final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); + + // If duration < 0, this is a cue to compute the duration based on the distance + if (duration < 0) { + duration = res.getInteger(R.integer.config_dropAnimMaxDuration); + if (dist < maxDist) { + duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); + } + duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); + } + + // Fall back to cubic ease out interpolator for the animation if none is specified + TimeInterpolator interpolator = null; + if (alphaInterpolator == null || motionInterpolator == null) { + interpolator = mCubicEaseOutInterpolator; + } + + // Animate the view + final float initAlpha = view.getAlpha(); + final float dropViewScale = view.getScaleX(); + AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + final int width = view.getMeasuredWidth(); + final int height = view.getMeasuredHeight(); + + float alphaPercent = alphaInterpolator == null ? percent : + alphaInterpolator.getInterpolation(percent); + float motionPercent = motionInterpolator == null ? percent : + motionInterpolator.getInterpolation(percent); + + float initialScaleX = initScaleX * dropViewScale; + float initialScaleY = initScaleY * dropViewScale; + float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); + float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); + float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); + + float fromLeft = from.left + (initialScaleX - 1f) * width / 2; + float fromTop = from.top + (initialScaleY - 1f) * height / 2; + + int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); + int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); + + int xPos = x - mDropView.getScrollX() + (mAnchorView != null + ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0); + int yPos = y - mDropView.getScrollY(); + + mDropView.setTranslationX(xPos); + mDropView.setTranslationY(yPos); + mDropView.setScaleX(scaleX); + mDropView.setScaleY(scaleY); + mDropView.setAlpha(alpha); + } + }; + animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, + anchorView); + } + + public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, + TimeInterpolator interpolator, final Runnable onCompleteRunnable, + final int animationEndStyle, View anchorView) { + // Clean up the previous animations + if (mDropAnim != null) mDropAnim.cancel(); + if (mFadeOutAnim != null) mFadeOutAnim.cancel(); + + // Show the drop view if it was previously hidden + mDropView = view; + mDropView.cancelAnimation(); + mDropView.resetLayoutParams(); + + // Set the anchor view if the page is scrolling + if (anchorView != null) { + mAnchorViewInitialScrollX = anchorView.getScrollX(); + } + mAnchorView = anchorView; + + // Create and start the animation + mDropAnim = new ValueAnimator(); + mDropAnim.setInterpolator(interpolator); + mDropAnim.setDuration(duration); + mDropAnim.setFloatValues(0f, 1f); + mDropAnim.addUpdateListener(updateCb); + mDropAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + switch (animationEndStyle) { + case ANIMATION_END_DISAPPEAR: + clearAnimatedView(); + break; + case ANIMATION_END_FADE_OUT: + fadeOutDragView(); + break; + case ANIMATION_END_REMAIN_VISIBLE: + break; + } + } + }); + mDropAnim.start(); + } + + public void clearAnimatedView() { + if (mDropAnim != null) { + mDropAnim.cancel(); + } + if (mDropView != null) { + mDragController.onDeferredEndDrag(mDropView); + } + mDropView = null; + invalidate(); + } + + public View getAnimatedView() { + return mDropView; + } + + private void fadeOutDragView() { + mFadeOutAnim = new ValueAnimator(); + mFadeOutAnim.setDuration(150); + mFadeOutAnim.setFloatValues(0f, 1f); + mFadeOutAnim.removeAllUpdateListeners(); + mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + + float alpha = 1 - percent; + mDropView.setAlpha(alpha); + } + }); + mFadeOutAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + if (mDropView != null) { + mDragController.onDeferredEndDrag(mDropView); + } + mDropView = null; + invalidate(); + } + }); + mFadeOutAnim.start(); + } + + @Override + public void onChildViewAdded(View parent, View child) { + updateChildIndices(); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + updateChildIndices(); + } + + private void updateChildIndices() { + if (mLauncher != null) { + mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace()); + mQsbIndex = indexOfChild(mLauncher.getSearchBar()); + } + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + // We don't want to prioritize the workspace drawing on top of the other children in + // landscape for the overscroll event. + if (LauncherApplication.isScreenLandscape(getContext())) { + return super.getChildDrawingOrder(childCount, i); + } + + if (mWorkspaceIndex == -1 || mQsbIndex == -1 || + mLauncher.getWorkspace().isDrawingBackgroundGradient()) { + return i; + } + + // This ensures that the workspace is drawn above the hotseat and qsb, + // except when the workspace is drawing a background gradient, in which + // case we want the workspace to stay behind these elements. + if (i == mQsbIndex) { + return mWorkspaceIndex; + } else if (i == mWorkspaceIndex) { + return mQsbIndex; + } else { + return i; + } + } + + private boolean mInScrollArea; + private Drawable mLeftHoverDrawable; + private Drawable mRightHoverDrawable; + + void onEnterScrollArea(int direction) { + mInScrollArea = true; + invalidate(); + } + + void onExitScrollArea() { + mInScrollArea = false; + invalidate(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + if (mInScrollArea && !LauncherApplication.isScreenLarge()) { + Workspace workspace = mLauncher.getWorkspace(); + int width = workspace.getWidth(); + Rect childRect = new Rect(); + getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect); + + int page = workspace.getNextPage(); + CellLayout leftPage = (CellLayout) workspace.getChildAt(page - 1); + CellLayout rightPage = (CellLayout) workspace.getChildAt(page + 1); + + if (leftPage != null && leftPage.getIsDragOverlapping()) { + mLeftHoverDrawable.setBounds(0, childRect.top, + mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom); + mLeftHoverDrawable.draw(canvas); + } else if (rightPage != null && rightPage.getIsDragOverlapping()) { + mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(), + childRect.top, width, childRect.bottom); + mRightHoverDrawable.draw(canvas); + } + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/DragScroller.java b/src/com/cyanogenmod/trebuchet/DragScroller.java new file mode 100644 index 000000000..5c47fc633 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DragScroller.java @@ -0,0 +1,40 @@ +/* + * 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.cyanogenmod.trebuchet; + +/** + * Handles scrolling while dragging + * + */ +public interface DragScroller { + void scrollLeft(); + void scrollRight(); + + /** + * The touch point has entered the scroll area; a scroll is imminent. + * This event will only occur while a drag is active. + * + * @param direction The scroll direction + */ + boolean onEnterScrollArea(int x, int y, int direction); + + /** + * The touch point has left the scroll area. + * NOTE: This may not be called, if a drop occurs inside the scroll area. + */ + boolean onExitScrollArea(); +} diff --git a/src/com/cyanogenmod/trebuchet/DragSource.java b/src/com/cyanogenmod/trebuchet/DragSource.java new file mode 100644 index 000000000..3f5ef0ed6 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DragSource.java @@ -0,0 +1,45 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.view.View; + +import com.cyanogenmod.trebuchet.DropTarget.DragObject; + +/** + * Interface defining an object that can originate a drag. + * + */ +public interface DragSource { + /** + * @return whether items dragged from this source supports + */ + boolean supportsFlingToDelete(); + + /** + * A callback specifically made back to the source after an item from this source has been flung + * to be deleted on a DropTarget. In such a situation, this method will be called after + * onDropCompleted, and more importantly, after the fling animation has completed. + */ + void onFlingToDeleteCompleted(); + + /** + * A callback made back to the source after an item from this source has been dropped on a + * DropTarget. + */ + void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success); +} diff --git a/src/com/cyanogenmod/trebuchet/DragView.java b/src/com/cyanogenmod/trebuchet/DragView.java new file mode 100644 index 000000000..9eefe2963 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DragView.java @@ -0,0 +1,292 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +import com.cyanogenmod.trebuchet.R; + +public class DragView extends View { + private static float sDragAlpha = 1f; + + private Bitmap mBitmap; + private Bitmap mCrossFadeBitmap; + private Paint mPaint; + private int mRegistrationX; + private int mRegistrationY; + + private Point mDragVisualizeOffset = null; + private Rect mDragRegion = null; + private DragLayer mDragLayer = null; + private boolean mHasDrawn = false; + private float mCrossFadeProgress = 0f; + + ValueAnimator mAnim; + private float mOffsetX = 0.0f; + private float mOffsetY = 0.0f; + private float mInitialScale = 1f; + + /** + * Construct the drag view. + *

+ * The registration point is the point inside our view that the touch events should + * be centered upon. + * + * @param launcher The Launcher instance + * @param bitmap The view that we're dragging around. We scale it up when we draw it. + * @param registrationX The x coordinate of the registration point. + * @param registrationY The y coordinate of the registration point. + */ + public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, + int left, int top, int width, int height, final float initialScale) { + super(launcher); + mDragLayer = launcher.getDragLayer(); + mInitialScale = initialScale; + + final Resources res = getResources(); + final float offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX); + final float offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY); + final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale); + final float scale = (width + scaleDps) / width; + + // Animate the view into the correct position + mAnim = ValueAnimator.ofFloat(0.0f, 1.0f); + mAnim.setDuration(150); + mAnim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float value = (Float) animation.getAnimatedValue(); + + final int deltaX = (int) ((value * offsetX) - mOffsetX); + final int deltaY = (int) ((value * offsetY) - mOffsetY); + + mOffsetX += deltaX; + mOffsetY += deltaY; + setScaleX(initialScale + (value * (scale - initialScale))); + setScaleY(initialScale + (value * (scale - initialScale))); + if (sDragAlpha != 1f) { + setAlpha(sDragAlpha * value + (1f - value)); + } + + if (getParent() == null) { + animation.cancel(); + } else { + setTranslationX(getTranslationX() + deltaX); + setTranslationY(getTranslationY() + deltaY); + } + } + }); + + mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height); + setDragRegion(new Rect(0, 0, width, height)); + + // The point in our scaled bitmap that the touch events are located + mRegistrationX = registrationX; + mRegistrationY = registrationY; + + // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass + int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + measure(ms, ms); + mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + } + + public float getOffsetY() { + return mOffsetY; + } + + public int getDragRegionLeft() { + return mDragRegion.left; + } + + public int getDragRegionTop() { + return mDragRegion.top; + } + + public int getDragRegionWidth() { + return mDragRegion.width(); + } + + public int getDragRegionHeight() { + return mDragRegion.height(); + } + + public void setDragVisualizeOffset(Point p) { + mDragVisualizeOffset = p; + } + + public Point getDragVisualizeOffset() { + return mDragVisualizeOffset; + } + + public void setDragRegion(Rect r) { + mDragRegion = r; + } + + public Rect getDragRegion() { + return mDragRegion; + } + + public float getInitialScale() { + return mInitialScale; + } + + public void updateInitialScaleToCurrentScale() { + mInitialScale = getScaleX(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); + } + + @Override + protected void onDraw(Canvas canvas) { + @SuppressWarnings("all") // suppress dead code warning + final boolean debug = false; + if (debug) { + Paint p = new Paint(); + p.setStyle(Paint.Style.FILL); + p.setColor(0x66ffffff); + canvas.drawRect(0, 0, getWidth(), getHeight(), p); + } + + mHasDrawn = true; + boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null; + if (crossFade) { + int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255; + mPaint.setAlpha(alpha); + } + canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); + if (crossFade) { + mPaint.setAlpha((int) (255 * mCrossFadeProgress)); + canvas.save(); + float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth(); + float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight(); + canvas.scale(sX, sY); + canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint); + canvas.restore(); + } + } + + public void setCrossFadeBitmap(Bitmap crossFadeBitmap) { + mCrossFadeBitmap = crossFadeBitmap; + } + + public void crossFade(int duration) { + ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + va.setDuration(duration); + va.setInterpolator(new DecelerateInterpolator(1.5f)); + va.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mCrossFadeProgress = animation.getAnimatedFraction(); + } + }); + va.start(); + } + + public void setColor(int color) { + if (mPaint == null) { + mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + } + if (color != 0) { + mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); + } else { + mPaint.setColorFilter(null); + } + invalidate(); + } + + public boolean hasDrawn() { + return mHasDrawn; + } + + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + mPaint.setAlpha((int) (255 * alpha)); + invalidate(); + } + + /** + * Create a window containing this view and show it. + * + * @param windowToken obtained from v.getWindowToken() from one of your views + * @param touchX the x coordinate the user touched in DragLayer coordinates + * @param touchY the y coordinate the user touched in DragLayer coordinates + */ + public void show(int touchX, int touchY) { + mDragLayer.addView(this); + + // Enable hw-layers on this view + setLayerType(View.LAYER_TYPE_HARDWARE, null); + + // Start the pick-up animation + DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0); + lp.width = mBitmap.getWidth(); + lp.height = mBitmap.getHeight(); + lp.customPosition = true; + setLayoutParams(lp); + setTranslationX(touchX - mRegistrationX); + setTranslationY(touchY - mRegistrationY); + mAnim.start(); + } + + public void cancelAnimation() { + if (mAnim != null && mAnim.isRunning()) { + mAnim.cancel(); + } + } + + public void resetLayoutParams() { + mOffsetX = mOffsetY = 0; + requestLayout(); + } + + /** + * Move the window containing this view. + * + * @param touchX the x coordinate the user touched in DragLayer coordinates + * @param touchY the y coordinate the user touched in DragLayer coordinates + */ + void move(int touchX, int touchY) { + setTranslationX(touchX - mRegistrationX + (int) mOffsetX); + setTranslationY(touchY - mRegistrationY + (int) mOffsetY); + } + + void remove() { + if (getParent() != null) { + // Disable hw-layers on this view + setLayerType(View.LAYER_TYPE_NONE, null); + + mDragLayer.removeView(DragView.this); + } + } +} + diff --git a/src/com/cyanogenmod/trebuchet/DrawableStateProxyView.java b/src/com/cyanogenmod/trebuchet/DrawableStateProxyView.java new file mode 100644 index 000000000..dcaad352b --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DrawableStateProxyView.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; + +import com.cyanogenmod.trebuchet.R; + +public class DrawableStateProxyView extends LinearLayout { + + private View mView; + private int mViewId; + + public DrawableStateProxyView(Context context) { + this(context, null); + } + + public DrawableStateProxyView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + + public DrawableStateProxyView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableStateProxyView, + defStyle, 0); + mViewId = a.getResourceId(R.styleable.DrawableStateProxyView_sourceViewId, -1); + a.recycle(); + + setFocusable(false); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if (mView == null) { + View parent = (View) getParent(); + mView = parent.findViewById(mViewId); + } + mView.setPressed(isPressed()); + mView.setHovered(isHovered()); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + return false; + } +} diff --git a/src/com/cyanogenmod/trebuchet/DropTarget.java b/src/com/cyanogenmod/trebuchet/DropTarget.java new file mode 100644 index 000000000..6a929cb5e --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/DropTarget.java @@ -0,0 +1,184 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.util.Log; + +/** + * Interface defining an object that can receive a drag. + * + */ +public interface DropTarget { + + public static final String TAG = "DropTarget"; + + class DragObject { + public int x = -1; + public int y = -1; + + /** X offset from the upper-left corner of the cell to where we touched. */ + public int xOffset = -1; + + /** Y offset from the upper-left corner of the cell to where we touched. */ + public int yOffset = -1; + + /** This indicates whether a drag is in final stages, either drop or cancel. It + * differentiates onDragExit, since this is called when the drag is ending, above + * the current drag target, or when the drag moves off the current drag object. + */ + public boolean dragComplete = false; + + /** The view that moves around while you drag. */ + public DragView dragView = null; + + /** The data associated with the object being dragged */ + public Object dragInfo = null; + + /** Where the drag originated */ + public DragSource dragSource = null; + + /** Post drag animation runnable */ + public Runnable postAnimationRunnable = null; + + /** Indicates that the drag operation was cancelled */ + public boolean cancelled = false; + + /** Defers removing the DragView from the DragLayer until after the drop animation. */ + public boolean deferDragViewCleanupPostAnimation = true; + + public DragObject() { + } + } + + public static class DragEnforcer implements DragController.DragListener { + int dragParity = 0; + + public DragEnforcer(Context context) { + Launcher launcher = (Launcher) context; + launcher.getDragController().addDragListener(this); + } + + void onDragEnter() { + dragParity++; + if (dragParity != 1) { + Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); + } + } + + void onDragExit() { + dragParity--; + if (dragParity != 0) { + Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); + } + } + + @Override + public void onDragStart(DragSource source, Object info, int dragAction) { + if (dragParity != 0) { + Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); + } + } + + @Override + public void onDragEnd() { + if (dragParity != 0) { + Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); + } + } + } + + /** + * 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 + * + * @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 dragView The DragView that's being dragged around on screen. + * @param dragInfo Data associated with the object being dragged + * + */ + void onDrop(DragObject dragObject); + + void onDragEnter(DragObject dragObject); + + void onDragOver(DragObject dragObject); + + void onDragExit(DragObject dragObject); + + /** + * Handle an object being dropped as a result of flinging to delete and will be called in place + * of onDrop(). (This is only called on objects that are set as the DragController's + * fling-to-delete target. + */ + void onFlingToDelete(DragObject dragObject, int x, int y, PointF vec); + + /** + * 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 dragView The DragView that's being dragged around on screen. + * @param dragInfo Data associated with the object being dragged + * + * @return The DropTarget to delegate to, or null to not delegate to another object. + */ + DropTarget getDropTargetDelegate(DragObject dragObject); + + /** + * 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 + * @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 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. + */ + boolean acceptDrop(DragObject dragObject); + + // These methods are implemented in Views + void getHitRect(Rect outRect); + void getLocationInDragLayer(int[] loc); + int getLeft(); + int getTop(); +} diff --git a/src/com/cyanogenmod/trebuchet/FastBitmapDrawable.java b/src/com/cyanogenmod/trebuchet/FastBitmapDrawable.java new file mode 100644 index 000000000..732e4c1ee --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/FastBitmapDrawable.java @@ -0,0 +1,108 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +class FastBitmapDrawable extends Drawable { + private Bitmap mBitmap; + private int mAlpha; + private int mWidth; + private int mHeight; + private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + + FastBitmapDrawable(Bitmap b) { + mAlpha = 255; + mBitmap = b; + if (b != null) { + mWidth = mBitmap.getWidth(); + mHeight = mBitmap.getHeight(); + } else { + mWidth = mHeight = 0; + } + } + + @Override + public void draw(Canvas canvas) { + final Rect r = getBounds(); + canvas.drawBitmap(mBitmap, r.left, r.top, mPaint); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mPaint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + mAlpha = alpha; + mPaint.setAlpha(alpha); + } + + public void setFilterBitmap(boolean filterBitmap) { + mPaint.setFilterBitmap(filterBitmap); + } + + public int getAlpha() { + return mAlpha; + } + + @Override + public int getIntrinsicWidth() { + return mWidth; + } + + @Override + public int getIntrinsicHeight() { + return mHeight; + } + + @Override + public int getMinimumWidth() { + return mWidth; + } + + @Override + public int getMinimumHeight() { + return mHeight; + } + + public void setBitmap(Bitmap b) { + mBitmap = b; + if (b != null) { + mWidth = mBitmap.getWidth(); + mHeight = mBitmap.getHeight(); + } else { + mWidth = mHeight = 0; + } + } + + public Bitmap getBitmap() { + return mBitmap; + } +} diff --git a/src/com/cyanogenmod/trebuchet/FocusHelper.java b/src/com/cyanogenmod/trebuchet/FocusHelper.java new file mode 100644 index 000000000..d87fc672e --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/FocusHelper.java @@ -0,0 +1,898 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.res.Configuration; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.TabHost; +import android.widget.TabWidget; + +import com.cyanogenmod.trebuchet.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * A keyboard listener we set on all the workspace icons. + */ +class IconKeyEventListener implements View.OnKeyListener { + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleIconKeyEvent(v, keyCode, event); + } +} + +/** + * A keyboard listener we set on all the workspace icons. + */ +class FolderKeyEventListener implements View.OnKeyListener { + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleFolderKeyEvent(v, keyCode, event); + } +} + +/** + * A keyboard listener we set on all the hotseat buttons. + */ +class HotseatIconKeyEventListener implements View.OnKeyListener { + public boolean onKey(View v, int keyCode, KeyEvent event) { + final Configuration configuration = v.getResources().getConfiguration(); + return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); + } +} + +/** + * A keyboard listener we set on the last tab button in AppsCustomize to jump to then + * market icon and vice versa. + */ +class AppsCustomizeTabKeyEventListener implements View.OnKeyListener { + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event); + } +} + +public class FocusHelper { + /** + * Private helper to get the parent TabHost in the view hiearchy. + */ + private static TabHost findTabHostParent(View v) { + ViewParent p = v.getParent(); + while (p != null && !(p instanceof TabHost)) { + p = p.getParent(); + } + return (TabHost) p; + } + + /** + * Handles key events in a AppsCustomize tab between the last tab view and the shop button. + */ + static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) { + final TabHost tabHost = findTabHostParent(v); + final ViewGroup contents = tabHost.getTabContentView(); + final View shop = tabHost.findViewById(R.id.market_button); + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the shop button if we aren't on it + if (v != shop) { + shop.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the content view (down is handled by the tab key handler otherwise) + if (v == shop) { + contents.requestFocus(); + wasHandled = true; + } + } + break; + default: break; + } + return wasHandled; + } + + /** + * Returns the Viewgroup containing page contents for the page at the index specified. + */ + private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) { + ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index); + if (page instanceof PagedViewCellLayout) { + // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren + page = (ViewGroup) page.getChildAt(0); + } + return page; + } + + /** + * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets. + */ + static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode, + KeyEvent e) { + + final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent(); + final PagedView container = (PagedView) parent.getParent(); + final TabHost tabHost = findTabHostParent(container); + final TabWidget tabs = tabHost.getTabWidget(); + final int widgetIndex = parent.indexOfChild(w); + final int widgetCount = parent.getChildCount(); + final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent)); + final int pageCount = container.getChildCount(); + final int cellCountX = parent.getCellCountX(); + final int cellCountY = parent.getCellCountY(); + final int x = widgetIndex % cellCountX; + final int y = widgetIndex / cellCountX; + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + ViewGroup newParent = null; + // Now that we load items in the bg asynchronously, we can't just focus + // child siblings willy-nilly + View child = null; + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous widget or the last widget on the previous page + if (widgetIndex > 0) { + parent.getChildAt(widgetIndex - 1).requestFocus(); + } else { + if (pageIndex > 0) { + newParent = getAppsCustomizePage(container, pageIndex - 1); + if (newParent != null) { + child = newParent.getChildAt(newParent.getChildCount() - 1); + if (child != null) child.requestFocus(); + } + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next widget or the first widget on the next page + if (widgetIndex < (widgetCount - 1)) { + parent.getChildAt(widgetIndex + 1).requestFocus(); + } else { + if (pageIndex < (pageCount - 1)) { + newParent = getAppsCustomizePage(container, pageIndex + 1); + if (newParent != null) { + child = newParent.getChildAt(0); + if (child != null) child.requestFocus(); + } + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select the closest icon in the previous row, otherwise select the tab bar + if (y > 0) { + int newWidgetIndex = ((y - 1) * cellCountX) + x; + child = parent.getChildAt(newWidgetIndex); + if (child != null) child.requestFocus(); + } else { + tabs.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the previous row, otherwise do nothing + if (y < (cellCountY - 1)) { + int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x); + child = parent.getChildAt(newWidgetIndex); + if (child != null) child.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (handleKeyEvent) { + // Simulate a click on the widget + View.OnClickListener clickListener = (View.OnClickListener) container; + clickListener.onClick(w); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_UP: + if (handleKeyEvent) { + // Select the first item on the previous page, or the first item on this page + // if there is no previous page + if (pageIndex > 0) { + newParent = getAppsCustomizePage(container, pageIndex - 1); + if (newParent != null) { + child = newParent.getChildAt(0); + } + } else { + child = parent.getChildAt(0); + } + if (child != null) child.requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_DOWN: + if (handleKeyEvent) { + // Select the first item on the next page, or the last item on this page + // if there is no next page + if (pageIndex < (pageCount - 1)) { + newParent = getAppsCustomizePage(container, pageIndex + 1); + if (newParent != null) { + child = newParent.getChildAt(0); + } + } else { + child = parent.getChildAt(widgetCount - 1); + } + if (child != null) child.requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first item on this page + child = parent.getChildAt(0); + if (child != null) child.requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last item on this page + parent.getChildAt(widgetCount - 1).requestFocus(); + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events in a PageViewCellLayout containing PagedViewIcons. + */ + static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) { + ViewGroup parentLayout; + ViewGroup itemContainer; + int countX; + int countY; + if (v.getParent() instanceof PagedViewCellLayoutChildren) { + itemContainer = (ViewGroup) v.getParent(); + parentLayout = (ViewGroup) itemContainer.getParent(); + countX = ((PagedViewCellLayout) parentLayout).getCellCountX(); + countY = ((PagedViewCellLayout) parentLayout).getCellCountY(); + } else { + itemContainer = parentLayout = (ViewGroup) v.getParent(); + countX = ((PagedViewGridLayout) parentLayout).getCellCountX(); + countY = ((PagedViewGridLayout) parentLayout).getCellCountY(); + } + + // Note we have an extra parent because of the + // PagedViewCellLayout/PagedViewCellLayoutChildren relationship + final PagedView container = (PagedView) parentLayout.getParent(); + final TabHost tabHost = findTabHostParent(container); + final TabWidget tabs = tabHost.getTabWidget(); + final int iconIndex = itemContainer.indexOfChild(v); + final int itemCount = itemContainer.getChildCount(); + final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout)); + final int pageCount = container.getChildCount(); + + final int x = iconIndex % countX; + final int y = iconIndex / countX; + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + ViewGroup newParent = null; + // Side pages do not always load synchronously, so check before focusing child siblings + // willy-nilly + View child = null; + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous icon or the last icon on the previous page + if (iconIndex > 0) { + itemContainer.getChildAt(iconIndex - 1).requestFocus(); + } else { + if (pageIndex > 0) { + newParent = getAppsCustomizePage(container, pageIndex - 1); + if (newParent != null) { + container.snapToPage(pageIndex - 1); + child = newParent.getChildAt(newParent.getChildCount() - 1); + if (child != null) child.requestFocus(); + } + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next icon or the first icon on the next page + if (iconIndex < (itemCount - 1)) { + itemContainer.getChildAt(iconIndex + 1).requestFocus(); + } else { + if (pageIndex < (pageCount - 1)) { + newParent = getAppsCustomizePage(container, pageIndex + 1); + if (newParent != null) { + container.snapToPage(pageIndex + 1); + child = newParent.getChildAt(0); + if (child != null) child.requestFocus(); + } + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select the closest icon in the previous row, otherwise select the tab bar + if (y > 0) { + int newiconIndex = ((y - 1) * countX) + x; + itemContainer.getChildAt(newiconIndex).requestFocus(); + } else { + tabs.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the previous row, otherwise do nothing + if (y < (countY - 1)) { + int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x); + itemContainer.getChildAt(newiconIndex).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (handleKeyEvent) { + // Simulate a click on the icon + View.OnClickListener clickListener = (View.OnClickListener) container; + clickListener.onClick(v); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_UP: + if (handleKeyEvent) { + // Select the first icon on the previous page, or the first icon on this page + // if there is no previous page + if (pageIndex > 0) { + newParent = getAppsCustomizePage(container, pageIndex - 1); + if (newParent != null) { + container.snapToPage(pageIndex - 1); + child = newParent.getChildAt(0); + if (child != null) child.requestFocus(); + } + } else { + itemContainer.getChildAt(0).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_DOWN: + if (handleKeyEvent) { + // Select the first icon on the next page, or the last icon on this page + // if there is no next page + if (pageIndex < (pageCount - 1)) { + newParent = getAppsCustomizePage(container, pageIndex + 1); + if (newParent != null) { + container.snapToPage(pageIndex + 1); + child = newParent.getChildAt(0); + if (child != null) child.requestFocus(); + } + } else { + itemContainer.getChildAt(itemCount - 1).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first icon on this page + itemContainer.getChildAt(0).requestFocus(); + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last icon on this page + itemContainer.getChildAt(itemCount - 1).requestFocus(); + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events in the tab widget. + */ + static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) { + if (!LauncherApplication.isScreenLarge()) return false; + + final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent(); + final TabHost tabHost = findTabHostParent(parent); + final ViewGroup contents = tabHost.getTabContentView(); + final int tabCount = parent.getTabCount(); + final int tabIndex = parent.getChildTabIndex(v); + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous tab + if (tabIndex > 0) { + parent.getChildTabViewAt(tabIndex - 1).requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next tab, or if the last tab has a focus right id, select that + if (tabIndex < (tabCount - 1)) { + parent.getChildTabViewAt(tabIndex + 1).requestFocus(); + } else { + if (v.getNextFocusRightId() != View.NO_ID) { + tabHost.findViewById(v.getNextFocusRightId()).requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + // Do nothing + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the content view + contents.requestFocus(); + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events in the workspace hotseat (bottom of the screen). + */ + static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) { + final ViewGroup parent = (ViewGroup) v.getParent(); + final ViewGroup launcher = (ViewGroup) parent.getParent(); + final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace); + final int buttonIndex = parent.indexOfChild(v); + final int buttonCount = parent.getChildCount(); + final int pageIndex = workspace.getCurrentPage(); + + // NOTE: currently we don't special case for the phone UI in different + // orientations, even though the hotseat is on the side in landscape mode. This + // is to ensure that accessibility consistency is maintained across rotations. + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous button, otherwise snap to the previous page + if (buttonIndex > 0) { + parent.getChildAt(buttonIndex - 1).requestFocus(); + } else { + workspace.snapToPage(pageIndex - 1); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next button, otherwise snap to the next page + if (buttonIndex < (buttonCount - 1)) { + parent.getChildAt(buttonIndex + 1).requestFocus(); + } else { + workspace.snapToPage(pageIndex + 1); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select the first bubble text view in the current page of the workspace + final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex); + final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets(); + final View newIcon = getIconInDirection(layout, children, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + workspace.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + // Do nothing + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Private helper method to get the CellLayoutChildren given a CellLayout index. + */ + private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex( + ViewGroup container, int i) { + ViewGroup parent = (ViewGroup) container.getChildAt(i); + return (ShortcutAndWidgetContainer) parent.getChildAt(0); + } + + /** + * Private helper method to sort all the CellLayout children in order of their (x,y) spatially + * from top left to bottom right. + */ + private static ArrayList getCellLayoutChildrenSortedSpatially(CellLayout layout, + ViewGroup parent) { + // First we order each the CellLayout children by their x,y coordinates + final int cellCountX = layout.getCountX(); + final int count = parent.getChildCount(); + ArrayList views = new ArrayList(); + for (int j = 0; j < count; ++j) { + views.add(parent.getChildAt(j)); + } + Collections.sort(views, new Comparator() { + @Override + public int compare(View lhs, View rhs) { + CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams(); + CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams(); + int lvIndex = (llp.cellY * cellCountX) + llp.cellX; + int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX; + return lvIndex - rvIndex; + } + }); + return views; + } + /** + * Private helper method to find the index of the next BubbleTextView or FolderIcon in the + * direction delta. + * + * @param delta either -1 or 1 depending on the direction we want to search + */ + private static View findIndexOfIcon(ArrayList views, int i, int delta) { + // Then we find the next BubbleTextView offset by delta from i + final int count = views.size(); + int newI = i + delta; + while (0 <= newI && newI < count) { + View newV = views.get(newI); + if (newV instanceof BubbleTextView || newV instanceof FolderIcon) { + return newV; + } + newI += delta; + } + return null; + } + private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i, + int delta) { + final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); + return findIndexOfIcon(views, i, delta); + } + private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v, + int delta) { + final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); + return findIndexOfIcon(views, views.indexOf(v), delta); + } + /** + * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction + * delta on the next line. + * + * @param delta either -1 or 1 depending on the line and direction we want to search + */ + private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v, + int lineDelta) { + final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); + final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); + final int cellCountY = layout.getCountY(); + final int row = lp.cellY; + final int newRow = row + lineDelta; + if (0 <= newRow && newRow < cellCountY) { + float closestDistance = Float.MAX_VALUE; + int closestIndex = -1; + int index = views.indexOf(v); + int endIndex = (lineDelta < 0) ? -1 : views.size(); + while (index != endIndex) { + View newV = views.get(index); + CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams(); + boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row); + if (satisfiesRow && + (newV instanceof BubbleTextView || newV instanceof FolderIcon)) { + float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) + + Math.pow(tmpLp.cellY - lp.cellY, 2)); + if (tmpDistance < closestDistance) { + closestIndex = index; + closestDistance = tmpDistance; + } + } + if (index <= endIndex) { + ++index; + } else { + --index; + } + } + if (closestIndex > -1) { + return views.get(closestIndex); + } + } + return null; + } + + /** + * Handles key events in a Workspace containing. + */ + static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { + ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); + final CellLayout layout = (CellLayout) parent.getParent(); + final Workspace workspace = (Workspace) layout.getParent(); + final ViewGroup launcher = (ViewGroup) workspace.getParent(); + final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar); + final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat); + int pageIndex = workspace.indexOfChild(layout); + int pageCount = workspace.getChildCount(); + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous icon or the last icon on the previous page if possible + View newIcon = getIconInDirection(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + if (pageIndex > 0) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); + newIcon = getIconInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the previous page + workspace.snapToPage(pageIndex - 1); + } + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next icon or the first icon on the next page if possible + View newIcon = getIconInDirection(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + if (pageIndex < (pageCount - 1)) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); + newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the next page + workspace.snapToPage(pageIndex + 1); + } + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select the closest icon in the previous line, otherwise select the tab bar + View newIcon = getClosestIconOnLine(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + wasHandled = true; + } else { + tabs.requestFocus(); + } + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the next line, otherwise select the button bar + View newIcon = getClosestIconOnLine(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + wasHandled = true; + } else if (hotseat != null) { + hotseat.requestFocus(); + } + } + break; + case KeyEvent.KEYCODE_PAGE_UP: + if (handleKeyEvent) { + // Select the first icon on the previous page or the first icon on this page + // if there is no previous page + if (pageIndex > 0) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); + View newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the previous page + workspace.snapToPage(pageIndex - 1); + } + } else { + View newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_PAGE_DOWN: + if (handleKeyEvent) { + // Select the first icon on the next page or the last icon on this page + // if there is no previous page + if (pageIndex < (pageCount - 1)) { + parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); + View newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + // Snap to the next page + workspace.snapToPage(pageIndex + 1); + } + } else { + View newIcon = getIconInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first icon on this page + View newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last icon on this page + View newIcon = getIconInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events for items in a Folder. + */ + static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) { + ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent(); + final CellLayout layout = (CellLayout) parent.getParent(); + final Folder folder = (Folder) layout.getParent(); + View title = folder.mFolderName; + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous icon + View newIcon = getIconInDirection(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next icon + View newIcon = getIconInDirection(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + title.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select the closest icon in the previous line + View newIcon = getClosestIconOnLine(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the next line + View newIcon = getClosestIconOnLine(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + title.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first icon on this page + View newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last icon on this page + View newIcon = getIconInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } +} diff --git a/src/com/cyanogenmod/trebuchet/FocusOnlyTabWidget.java b/src/com/cyanogenmod/trebuchet/FocusOnlyTabWidget.java new file mode 100644 index 000000000..795b621e6 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/FocusOnlyTabWidget.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TabWidget; + +public class FocusOnlyTabWidget extends TabWidget { + public FocusOnlyTabWidget(Context context) { + super(context); + } + + public FocusOnlyTabWidget(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public View getSelectedTab() { + final int count = getTabCount(); + for (int i = 0; i < count; ++i) { + View v = getChildTabViewAt(i); + if (v.isSelected()) { + return v; + } + } + return null; + } + + public int getChildTabIndex(View v) { + final int tabCount = getTabCount(); + for (int i = 0; i < tabCount; ++i) { + if (getChildTabViewAt(i) == v) { + return i; + } + } + return -1; + } + + public void setCurrentTabToFocusedTab() { + View tab = null; + int index = -1; + final int count = getTabCount(); + for (int i = 0; i < count; ++i) { + View v = getChildTabViewAt(i); + if (v.hasFocus()) { + tab = v; + index = i; + break; + } + } + if (index > -1) { + super.setCurrentTab(index); + super.onFocusChange(tab, true); + } + } + public void superOnFocusChange(View v, boolean hasFocus) { + super.onFocusChange(v, hasFocus); + } + + @Override + public void onFocusChange(android.view.View v, boolean hasFocus) { + if (v == this && hasFocus && getTabCount() > 0) { + getSelectedTab().requestFocus(); + return; + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/Folder.java b/src/com/cyanogenmod/trebuchet/Folder.java new file mode 100644 index 000000000..7f56537c6 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Folder.java @@ -0,0 +1,1105 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.InputType; +import android.text.Selection; +import android.text.Spannable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ActionMode; +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.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.FolderInfo.FolderListener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Represents a set of icons chosen by the user or generated by the system. + */ +public class Folder extends LinearLayout implements DragSource, View.OnClickListener, + View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, + View.OnFocusChangeListener { + + @SuppressWarnings("unused") + private static final String TAG = "Launcher.Folder"; + + protected DragController mDragController; + protected Launcher mLauncher; + protected FolderInfo mInfo; + + static final int STATE_NONE = -1; + static final int STATE_SMALL = 0; + static final int STATE_ANIMATING = 1; + static final int STATE_OPEN = 2; + + private int mExpandDuration; + protected CellLayout mContent; + private final LayoutInflater mInflater; + private final IconCache mIconCache; + private int mState = STATE_NONE; + private static final int REORDER_ANIMATION_DURATION = 230; + private static final int ON_EXIT_CLOSE_DELAY = 800; + private boolean mRearrangeOnClose = false; + private FolderIcon mFolderIcon; + private int mMaxCountX; + private int mMaxCountY; + private int mMaxNumItems; + private ArrayList mItemsInReadingOrder = new ArrayList(); + private Drawable mIconDrawable; + boolean mItemsInvalidated = false; + private ShortcutInfo mCurrentDragInfo; + private View mCurrentDragView; + boolean mSuppressOnAdd = false; + private int[] mTargetCell = new int[2]; + private int[] mPreviousTargetCell = new int[2]; + private int[] mEmptyCell = new int[2]; + private Alarm mReorderAlarm = new Alarm(); + private Alarm mOnExitAlarm = new Alarm(); + private int mFolderNameHeight; + private Rect mTempRect = new Rect(); + private boolean mDragInProgress = false; + private boolean mDeleteFolderOnDropCompleted = false; + private boolean mSuppressFolderDeletion = false; + private boolean mItemAddedBackToSelfViaIcon = false; + FolderEditText mFolderName; + private float mFolderIconPivotX; + private float mFolderIconPivotY; + + private boolean mIsEditingName = false; + private InputMethodManager mInputMethodManager; + + private static String sDefaultFolderName; + private static String sHintText; + private ObjectAnimator mOpenCloseAnimator; + + /** + * Used to inflate the Workspace from XML. + * + * @param context The application's context. + * @param attrs The attribtues set containing the Workspace's customization values. + */ + public Folder(Context context, AttributeSet attrs) { + super(context, attrs); + setAlwaysDrawnWithCacheEnabled(false); + mInflater = LayoutInflater.from(context); + mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache(); + + Resources res = getResources(); + mMaxCountX = res.getInteger(R.integer.folder_max_count_x); + mMaxCountY = res.getInteger(R.integer.folder_max_count_y); + mMaxNumItems = res.getInteger(R.integer.folder_max_num_items); + if (mMaxCountX < 0 || mMaxCountY < 0 || mMaxNumItems < 0) { + mMaxCountX = LauncherModel.getCellCountX(); + mMaxCountY = LauncherModel.getCellCountY(); + mMaxNumItems = mMaxCountX * mMaxCountY; + } + + mInputMethodManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration); + + if (sDefaultFolderName == null) { + sDefaultFolderName = res.getString(R.string.folder_name); + } + if (sHintText == null) { + sHintText = res.getString(R.string.folder_hint_text); + } + mLauncher = (Launcher) context; + // We need this view to be focusable in touch mode so that when text editing of the folder + // name is complete, we have something to focus on, thus hiding the cursor and giving + // reliable behvior when clicking the text field (since it will always gain focus on click). + setFocusableInTouchMode(true); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContent = (CellLayout) findViewById(R.id.folder_content); + mContent.setGridSize(0, 0); + mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); + mFolderName = (FolderEditText) findViewById(R.id.folder_name); + mFolderName.setFolder(this); + mFolderName.setOnFocusChangeListener(this); + + // We find out how tall the text view wants to be (it is set to wrap_content), so that + // we can allocate the appropriate amount of space for it. + int measureSpec = MeasureSpec.UNSPECIFIED; + mFolderName.measure(measureSpec, measureSpec); + mFolderNameHeight = mFolderName.getMeasuredHeight(); + + // We disable action mode for now since it messes up the view on phones + mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); + mFolderName.setOnEditorActionListener(this); + mFolderName.setSelectAllOnFocus(true); + mFolderName.setInputType(mFolderName.getInputType() | + InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); + } + + private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + }; + + public void onClick(View v) { + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + // refactor this code from Folder + ShortcutInfo item = (ShortcutInfo) tag; + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + item.intent.setSourceBounds(new Rect(pos[0], pos[1], + pos[0] + v.getWidth(), pos[1] + v.getHeight())); + + mLauncher.startActivitySafely(v, item.intent, item); + } + } + + public boolean onLongClick(View v) { + // Return if global dragging is not enabled + if (!mLauncher.isDraggingEnabled()) return true; + + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + ShortcutInfo item = (ShortcutInfo) tag; + if (!v.isInTouchMode()) { + return false; + } + + mLauncher.dismissFolderCling(null); + + mLauncher.getWorkspace().onDragStartedWithItem(v); + mLauncher.getWorkspace().beginDragShared(v, this); + mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; + + mCurrentDragInfo = item; + mEmptyCell[0] = item.cellX; + mEmptyCell[1] = item.cellY; + mCurrentDragView = v; + + mContent.removeView(mCurrentDragView); + mInfo.remove(mCurrentDragInfo); + mDragInProgress = true; + mItemAddedBackToSelfViaIcon = false; + } + return true; + } + + public boolean isEditingName() { + return mIsEditingName; + } + + public void startEditingFolderName() { + mFolderName.setHint(""); + mIsEditingName = true; + } + + public void dismissEditingName() { + mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + doneEditingFolderName(true); + } + + public void doneEditingFolderName(boolean commit) { + mFolderName.setHint(sHintText); + // Convert to a string here to ensure that no other state associated with the text field + // gets saved. + String newTitle = mFolderName.getText().toString(); + mInfo.setTitle(newTitle); + LauncherModel.updateItemInDatabase(mLauncher, mInfo); + + if (commit) { + sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + String.format(getContext().getString(R.string.folder_renamed), newTitle)); + } + // In order to clear the focus from the text field, we set the focus on ourself. This + // ensures that every time the field is clicked, focus is gained, giving reliable behavior. + requestFocus(); + + Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); + mIsEditingName = false; + } + + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + dismissEditingName(); + return true; + } + return false; + } + + public View getEditTextRegion() { + return mFolderName; + } + + public Drawable getDragDrawable() { + return mIconDrawable; + } + + /** + * We need to handle touch events to prevent them from falling through to the workspace below. + */ + @Override + public boolean onTouchEvent(MotionEvent ev) { + return true; + } + + public void setDragController(DragController dragController) { + mDragController = dragController; + } + + void setFolderIcon(FolderIcon icon) { + mFolderIcon = icon; + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // When the folder gets focus, we don't want to announce the list of items. + return true; + } + + /** + * @return the FolderInfo object associated with this folder + */ + FolderInfo getInfo() { + return mInfo; + } + + private class GridComparator implements Comparator { + int mNumCols; + public GridComparator(int numCols) { + mNumCols = numCols; + } + + @Override + public int compare(ShortcutInfo lhs, ShortcutInfo rhs) { + int lhIndex = lhs.cellY * mNumCols + lhs.cellX; + int rhIndex = rhs.cellY * mNumCols + rhs.cellX; + return (lhIndex - rhIndex); + } + } + + private void placeInReadingOrder(ArrayList items) { + int maxX = 0; + int count = items.size(); + for (int i = 0; i < count; i++) { + ShortcutInfo item = items.get(i); + if (item.cellX > maxX) { + maxX = item.cellX; + } + } + + GridComparator gridComparator = new GridComparator(maxX + 1); + Collections.sort(items, gridComparator); + final int countX = mContent.getCountX(); + for (int i = 0; i < count; i++) { + int x = i % countX; + int y = i / countX; + ShortcutInfo item = items.get(i); + item.cellX = x; + item.cellY = y; + } + } + + void bind(FolderInfo info) { + mInfo = info; + ArrayList children = info.contents; + ArrayList overflow = new ArrayList(); + setupContentForNumItems(children.size()); + placeInReadingOrder(children); + int count = 0; + for (int i = 0; i < children.size(); i++) { + ShortcutInfo child = (ShortcutInfo) children.get(i); + if (!createAndAddShortcut(child)) { + overflow.add(child); + } else { + count++; + } + } + + // We rearrange the items in case there are any empty gaps + setupContentForNumItems(count); + + // If our folder has too many items we prune them from the list. This is an issue + // when upgrading from the old Folders implementation which could contain an unlimited + // number of items. + for (ShortcutInfo item: overflow) { + mInfo.remove(item); + LauncherModel.deleteItemFromDatabase(mLauncher, item); + } + + mItemsInvalidated = true; + updateTextViewFocus(); + mInfo.addListener(this); + + if (!sDefaultFolderName.contentEquals(mInfo.title)) { + mFolderName.setText(mInfo.title); + } else { + mFolderName.setText(""); + } + updateItemLocationsInDatabase(); + } + + /** + * Creates a new UserFolder, inflated from R.layout.user_folder. + * + * @param context The application's context. + * + * @return A new UserFolder. + */ + static Folder fromXml(Context context) { + return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); + } + + /** + * This method is intended to make the UserFolder to be visually identical in size and position + * to its associated FolderIcon. This allows for a seamless transition into the expanded state. + */ + private void positionAndSizeAsIcon() { + if (!(getParent() instanceof DragLayer)) return; + setScaleX(0.8f); + setScaleY(0.8f); + setAlpha(0f); + mState = STATE_SMALL; + } + + public void animateOpen() { + positionAndSizeAsIcon(); + + if (!(getParent() instanceof DragLayer)) return; + centerAboutIcon(); + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); + PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); + final ObjectAnimator oa = mOpenCloseAnimator = + ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); + + oa.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + String.format(getContext().getString(R.string.folder_opened), + mContent.getCountX(), mContent.getCountY())); + mState = STATE_ANIMATING; + } + @Override + public void onAnimationEnd(Animator animation) { + mState = STATE_OPEN; + setLayerType(LAYER_TYPE_NONE, null); + Cling cling = mLauncher.showFirstRunFoldersCling(); + if (cling != null) { + cling.bringToFront(); + } + setFocusOnFirstChild(); + } + }); + oa.setDuration(mExpandDuration); + setLayerType(LAYER_TYPE_HARDWARE, null); + buildLayer(); + post(new Runnable() { + public void run() { + // Check if the animator changed in the meantime + if (oa != mOpenCloseAnimator) + return; + oa.start(); + } + }); + } + + private void sendCustomAccessibilityEvent(int type, String text) { + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(type); + onInitializeAccessibilityEvent(event); + event.getText().add(text); + accessibilityManager.sendAccessibilityEvent(event); + } + } + + private void setFocusOnFirstChild() { + View firstChild = mContent.getChildAt(0, 0); + if (firstChild != null) { + firstChild.requestFocus(); + } + } + + public void animateClosed() { + if (!(getParent() instanceof DragLayer)) return; + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); + PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); + final ObjectAnimator oa = mOpenCloseAnimator = + ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); + + oa.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onCloseComplete(); + setLayerType(LAYER_TYPE_NONE, null); + mState = STATE_SMALL; + } + @Override + public void onAnimationStart(Animator animation) { + sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.folder_closed)); + mState = STATE_ANIMATING; + } + }); + oa.setDuration(mExpandDuration); + setLayerType(LAYER_TYPE_HARDWARE, null); + buildLayer(); + post(new Runnable() { + public void run() { + // Check if the animator changed in the meantime + if (oa != mOpenCloseAnimator) + return; + oa.start(); + } + }); + } + + void notifyDataSetChanged() { + // recreate all the children if the data set changes under us. We may want to do this more + // intelligently (ie just removing the views that should no longer exist) + mContent.removeAllViewsInLayout(); + bind(mInfo); + } + + public boolean acceptDrop(DragObject d) { + final ItemInfo item = (ItemInfo) d.dragInfo; + final int itemType = item.itemType; + return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || + itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && + !isFull()); + } + + protected boolean findAndSetEmptyCells(ShortcutInfo item) { + int[] emptyCell = new int[2]; + if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { + item.cellX = emptyCell[0]; + item.cellY = emptyCell[1]; + return true; + } else { + return false; + } + } + + protected boolean createAndAddShortcut(ShortcutInfo item) { + final TextView textView = + (TextView) mInflater.inflate(R.layout.application, this, false); + textView.setCompoundDrawablesWithIntrinsicBounds(null, + new FastBitmapDrawable(item.getIcon(mIconCache)), null, null); + textView.setText(item.title); + textView.setTag(item); + + textView.setOnClickListener(this); + textView.setOnLongClickListener(this); + + // We need to check here to verify that the given item's location isn't already occupied + // by another item. + if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0 + || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) { + // This shouldn't happen, log it. + Log.e(TAG, "Folder order not properly persisted during bind"); + if (!findAndSetEmptyCells(item)) { + return false; + } + } + + CellLayout.LayoutParams lp = + new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); + boolean insert = false; + textView.setOnKeyListener(new FolderKeyEventListener()); + mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); + return true; + } + + public void onDragEnter(DragObject d) { + mPreviousTargetCell[0] = -1; + mPreviousTargetCell[1] = -1; + mOnExitAlarm.cancelAlarm(); + } + + OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + realTimeReorder(mEmptyCell, mTargetCell); + } + }; + + boolean readingOrderGreaterThan(int[] v1, int[] v2) { + if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) { + return true; + } else { + return false; + } + } + + private void realTimeReorder(int[] empty, int[] target) { + boolean wrap; + int startX; + int endX; + int startY; + int delay = 0; + float delayAmount = 30; + if (readingOrderGreaterThan(target, empty)) { + wrap = empty[0] >= mContent.getCountX() - 1; + startY = wrap ? empty[1] + 1 : empty[1]; + for (int y = startY; y <= target[1]; y++) { + startX = y == empty[1] ? empty[0] + 1 : 0; + endX = y < target[1] ? mContent.getCountX() - 1 : target[0]; + for (int x = startX; x <= endX; x++) { + View v = mContent.getChildAt(x,y); + if (mContent.animateChildToPosition(v, empty[0], empty[1], + REORDER_ANIMATION_DURATION, delay, true, true)) { + empty[0] = x; + empty[1] = y; + delay += delayAmount; + delayAmount *= 0.9; + } + } + } + } else { + wrap = empty[0] == 0; + startY = wrap ? empty[1] - 1 : empty[1]; + for (int y = startY; y >= target[1]; y--) { + startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1; + endX = y > target[1] ? 0 : target[0]; + for (int x = startX; x >= endX; x--) { + View v = mContent.getChildAt(x,y); + if (mContent.animateChildToPosition(v, empty[0], empty[1], + REORDER_ANIMATION_DURATION, delay, true, true)) { + empty[0] = x; + empty[1] = y; + delay += delayAmount; + delayAmount *= 0.9; + } + } + } + } + } + + public void onDragOver(DragObject d) { + float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null); + mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell); + + if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) { + mReorderAlarm.cancelAlarm(); + mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); + mReorderAlarm.setAlarm(150); + mPreviousTargetCell[0] = mTargetCell[0]; + mPreviousTargetCell[1] = mTargetCell[1]; + } + } + + // This is used to compute the visual center of the dragView. The idea is that + // the visual center represents the user's interpretation of where the item is, and hence + // is the appropriate point to use when determining drop location. + private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, + DragView dragView, float[] recycle) { + float res[]; + if (recycle == null) { + res = new float[2]; + } else { + res = recycle; + } + + // These represent the visual top and left of drag view if a dragRect was provided. + // If a dragRect was not provided, then they correspond to the actual view left and + // top, as the dragRect is in that case taken to be the entire dragView. + // R.dimen.dragViewOffsetY. + int left = x - xOffset; + int top = y - yOffset; + + // In order to find the visual center, we shift by half the dragRect + res[0] = left + dragView.getDragRegion().width() / 2; + res[1] = top + dragView.getDragRegion().height() / 2; + + return res; + } + + OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + completeDragExit(); + } + }; + + public void completeDragExit() { + mLauncher.closeFolder(); + mCurrentDragInfo = null; + mCurrentDragView = null; + mSuppressOnAdd = false; + mRearrangeOnClose = true; + } + + public void onDragExit(DragObject d) { + // We only close the folder if this is a true drag exit, ie. not because a drop + // has occurred above the folder. + if (!d.dragComplete) { + mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); + mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); + } + mReorderAlarm.cancelAlarm(); + } + + public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, + boolean success) { + if (success) { + if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) { + replaceFolderWithFinalItem(); + } + } else { + // The drag failed, we need to return the item to the folder + mFolderIcon.onDrop(d); + + // We're going to trigger a "closeFolder" which may occur before this item has + // been added back to the folder -- this could cause the folder to be deleted + if (mOnExitAlarm.alarmPending()) { + mSuppressFolderDeletion = true; + } + } + + if (target != this) { + if (mOnExitAlarm.alarmPending()) { + mOnExitAlarm.cancelAlarm(); + completeDragExit(); + } + } + mDeleteFolderOnDropCompleted = false; + mDragInProgress = false; + mItemAddedBackToSelfViaIcon = false; + mCurrentDragInfo = null; + mCurrentDragView = null; + mSuppressOnAdd = false; + + // Reordering may have occured, and we need to save the new item locations. We do this once + // at the end to prevent unnecessary database operations. + updateItemLocationsInDatabase(); + } + + @Override + public boolean supportsFlingToDelete() { + return true; + } + + public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { + // Do nothing + } + + @Override + public void onFlingToDeleteCompleted() { + // Do nothing + } + + private void updateItemLocationsInDatabase() { + ArrayList list = getItemsInReadingOrder(); + for (int i = 0; i < list.size(); i++) { + View v = list.get(i); + ItemInfo info = (ItemInfo) v.getTag(); + LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0, + info.cellX, info.cellY); + } + } + + public void notifyDrop() { + if (mDragInProgress) { + mItemAddedBackToSelfViaIcon = true; + } + } + + public boolean isDropEnabled() { + return true; + } + + public DropTarget getDropTargetDelegate(DragObject d) { + return null; + } + + private void setupContentDimensions(int count) { + ArrayList list = getItemsInReadingOrder(); + + int countX = mContent.getCountX(); + int countY = mContent.getCountY(); + boolean done = false; + + while (!done) { + int oldCountX = countX; + int oldCountY = countY; + if (countX * countY < count) { + // Current grid is too small, expand it + if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) { + countX++; + } else if (countY < mMaxCountY) { + countY++; + } + if (countY == 0) countY++; + } else if ((countY - 1) * countX >= count && countY >= countX) { + countY = Math.max(0, countY - 1); + } else if ((countX - 1) * countY >= count) { + countX = Math.max(0, countX - 1); + } + done = countX == oldCountX && countY == oldCountY; + } + mContent.setGridSize(countX, countY); + arrangeChildren(list); + } + + public boolean isFull() { + return getItemCount() >= mMaxNumItems; + } + + private void centerAboutIcon() { + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + + int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); + int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight() + + mFolderNameHeight; + DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); + + parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect); + + int centerX = mTempRect.centerX(); + int centerY = mTempRect.centerY(); + int centeredLeft = centerX - width / 2; + int centeredTop = centerY - height / 2; + + int currentPage = mLauncher.getWorkspace().getCurrentPage(); + // In case the workspace is scrolling, we need to use the final scroll to compute + // the folders bounds. + mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage); + // We first fetch the currently visible CellLayoutChildren + CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage); + ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets(); + Rect bounds = new Rect(); + parent.getDescendantRectRelativeToSelf(boundingLayout, bounds); + // We reset the workspaces scroll + mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage); + + // We need to bound the folder to the currently visible CellLayoutChildren + int left = Math.min(Math.max(bounds.left, centeredLeft), + bounds.left + bounds.width() - width); + int top = Math.min(Math.max(bounds.top, centeredTop), + bounds.top + bounds.height() - height); + // If the folder doesn't fit within the bounds, center it about the desired bounds + if (width >= bounds.width()) { + left = bounds.left + (bounds.width() - width) / 2; + } + if (height >= bounds.height()) { + top = bounds.top + (bounds.height() - height) / 2; + } + + int folderPivotX = width / 2 + (centeredLeft - left); + int folderPivotY = height / 2 + (centeredTop - top); + setPivotX(folderPivotX); + setPivotY(folderPivotY); + mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * + (1.0f * folderPivotX / width)); + mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * + (1.0f * folderPivotY / height)); + + lp.width = width; + lp.height = height; + lp.x = left; + lp.y = top; + } + + float getPivotXForIconAnimation() { + return mFolderIconPivotX; + } + float getPivotYForIconAnimation() { + return mFolderIconPivotY; + } + + private void setupContentForNumItems(int count) { + setupContentDimensions(count); + + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + if (lp == null) { + lp = new DragLayer.LayoutParams(0, 0); + lp.customPosition = true; + setLayoutParams(lp); + } + centerAboutIcon(); + } + + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); + int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight() + + mFolderNameHeight; + + int contentWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(), + MeasureSpec.EXACTLY); + int contentHeightSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredHeight(), + MeasureSpec.EXACTLY); + mContent.measure(contentWidthSpec, contentHeightSpec); + + mFolderName.measure(contentWidthSpec, + MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); + setMeasuredDimension(width, height); + } + + private void arrangeChildren(ArrayList list) { + int[] vacant = new int[2]; + if (list == null) { + list = getItemsInReadingOrder(); + } + mContent.removeAllViews(); + + for (int i = 0; i < list.size(); i++) { + View v = list.get(i); + mContent.getVacantCell(vacant, 1, 1); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); + lp.cellX = vacant[0]; + lp.cellY = vacant[1]; + ItemInfo info = (ItemInfo) v.getTag(); + if (info.cellX != vacant[0] || info.cellY != vacant[1]) { + info.cellX = vacant[0]; + info.cellY = vacant[1]; + LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0, + info.cellX, info.cellY); + } + boolean insert = false; + mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); + } + mItemsInvalidated = true; + } + + public int getItemCount() { + return mContent.getShortcutsAndWidgets().getChildCount(); + } + + public View getItemAt(int index) { + return mContent.getShortcutsAndWidgets().getChildAt(index); + } + + private void onCloseComplete() { + DragLayer parent = (DragLayer) getParent(); + if (parent != null) { + parent.removeView(this); + } + mDragController.removeDropTarget((DropTarget) this); + clearFocus(); + mFolderIcon.requestFocus(); + + if (mRearrangeOnClose) { + setupContentForNumItems(getItemCount()); + mRearrangeOnClose = false; + } + if (getItemCount() <= 1) { + if (!mDragInProgress && !mSuppressFolderDeletion) { + replaceFolderWithFinalItem(); + } else if (mDragInProgress) { + mDeleteFolderOnDropCompleted = true; + } + } + mSuppressFolderDeletion = false; + } + + private void replaceFolderWithFinalItem() { + ItemInfo finalItem = null; + + if (getItemCount() == 1) { + finalItem = mInfo.contents.get(0); + } + + // Remove the folder completely + CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screen); + cellLayout.removeView(mFolderIcon); + if (mFolderIcon instanceof DropTarget) { + mDragController.removeDropTarget((DropTarget) mFolderIcon); + } + mLauncher.removeFolder(mInfo); + + if (finalItem != null) { + LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, + mInfo.screen, mInfo.cellX, mInfo.cellY); + } + LauncherModel.deleteItemFromDatabase(mLauncher, mInfo); + + // Add the last remaining child to the workspace in place of the folder + if (finalItem != null) { + View child = mLauncher.createShortcut(R.layout.application, cellLayout, + (ShortcutInfo) finalItem); + + mLauncher.getWorkspace().addInScreen(child, mInfo.container, mInfo.screen, mInfo.cellX, + mInfo.cellY, mInfo.spanX, mInfo.spanY); + } + } + + // This method keeps track of the last item in the folder for the purposes + // of keyboard focus + private void updateTextViewFocus() { + View lastChild = getItemAt(getItemCount() - 1); + getItemAt(getItemCount() - 1); + if (lastChild != null) { + mFolderName.setNextFocusDownId(lastChild.getId()); + mFolderName.setNextFocusRightId(lastChild.getId()); + mFolderName.setNextFocusLeftId(lastChild.getId()); + mFolderName.setNextFocusUpId(lastChild.getId()); + } + } + + public void onDrop(DragObject d) { + ShortcutInfo item; + if (d.dragInfo instanceof ApplicationInfo) { + // Came from all apps -- make a copy + item = ((ApplicationInfo) d.dragInfo).makeShortcut(); + item.spanX = 1; + item.spanY = 1; + } else { + item = (ShortcutInfo) d.dragInfo; + } + // Dragged from self onto self, currently this is the only path possible, however + // we keep this as a distinct code path. + if (item == mCurrentDragInfo) { + ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag(); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams(); + si.cellX = lp.cellX = mEmptyCell[0]; + si.cellX = lp.cellY = mEmptyCell[1]; + mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true); + if (d.dragView.hasDrawn()) { + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView); + } else { + d.deferDragViewCleanupPostAnimation = false; + mCurrentDragView.setVisibility(VISIBLE); + } + mItemsInvalidated = true; + setupContentDimensions(getItemCount()); + mSuppressOnAdd = true; + } + mInfo.add(item); + } + + public void onAdd(ShortcutInfo item) { + mItemsInvalidated = true; + // If the item was dropped onto this open folder, we have done the work associated + // with adding the item to the folder, as indicated by mSuppressOnAdd being set + if (mSuppressOnAdd) return; + if (!findAndSetEmptyCells(item)) { + // The current layout is full, can we expand it? + setupContentForNumItems(getItemCount() + 1); + findAndSetEmptyCells(item); + } + createAndAddShortcut(item); + LauncherModel.addOrMoveItemInDatabase( + mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); + } + + public void onRemove(ShortcutInfo item) { + mItemsInvalidated = true; + // If this item is being dragged from this open folder, we have already handled + // the work associated with removing the item, so we don't have to do anything here. + if (item == mCurrentDragInfo) return; + View v = getViewForInfo(item); + mContent.removeView(v); + if (mState == STATE_ANIMATING) { + mRearrangeOnClose = true; + } else { + setupContentForNumItems(getItemCount()); + } + if (getItemCount() <= 1) { + replaceFolderWithFinalItem(); + } + } + + private View getViewForInfo(ShortcutInfo item) { + for (int j = 0; j < mContent.getCountY(); j++) { + for (int i = 0; i < mContent.getCountX(); i++) { + View v = mContent.getChildAt(i, j); + if (v.getTag() == item) { + return v; + } + } + } + return null; + } + + public void onItemsChanged() { + updateTextViewFocus(); + } + + public void onTitleChanged(CharSequence title) { + } + + public ArrayList getItemsInReadingOrder() { + return getItemsInReadingOrder(true); + } + + public ArrayList getItemsInReadingOrder(boolean includeCurrentDragItem) { + if (mItemsInvalidated) { + mItemsInReadingOrder.clear(); + for (int j = 0; j < mContent.getCountY(); j++) { + for (int i = 0; i < mContent.getCountX(); i++) { + View v = mContent.getChildAt(i, j); + if (v != null) { + ShortcutInfo info = (ShortcutInfo) v.getTag(); + if (info != mCurrentDragInfo || includeCurrentDragItem) { + mItemsInReadingOrder.add(v); + } + } + } + } + mItemsInvalidated = false; + } + return mItemsInReadingOrder; + } + + public void getLocationInDragLayer(int[] loc) { + mLauncher.getDragLayer().getLocationInDragLayer(this, loc); + } + + public void onFocusChange(View v, boolean hasFocus) { + if (v == mFolderName && hasFocus) { + startEditingFolderName(); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/FolderEditText.java b/src/com/cyanogenmod/trebuchet/FolderEditText.java new file mode 100644 index 000000000..4504460bf --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/FolderEditText.java @@ -0,0 +1,36 @@ +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.EditText; + +public class FolderEditText extends EditText { + + private Folder mFolder; + + public FolderEditText(Context context) { + super(context); + } + + public FolderEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FolderEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setFolder(Folder folder) { + mFolder = folder; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + // Catch the back button on the soft keyboard so that we can just close the activity + if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) { + mFolder.doneEditingFolderName(true); + } + return super.onKeyPreIme(keyCode, event); + } +} diff --git a/src/com/cyanogenmod/trebuchet/FolderIcon.java b/src/com/cyanogenmod/trebuchet/FolderIcon.java new file mode 100644 index 000000000..4a5ab5099 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/FolderIcon.java @@ -0,0 +1,631 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.DropTarget.DragObject; +import com.cyanogenmod.trebuchet.FolderInfo.FolderListener; + +import java.util.ArrayList; + +/** + * An icon that can appear on in the workspace representing an {@link UserFolder}. + */ +public class FolderIcon extends LinearLayout implements FolderListener { + private Launcher mLauncher; + Folder mFolder; + FolderInfo mInfo; + private static boolean sStaticValuesDirty = true; + + private CheckLongPressHelper mLongPressHelper; + + // The number of icons to display in the + private static final int NUM_ITEMS_IN_PREVIEW = 3; + private static final int CONSUMPTION_ANIMATION_DURATION = 100; + private static final int DROP_IN_ANIMATION_DURATION = 400; + private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; + + // The degree to which the inner ring grows when accepting drop + private static final float INNER_RING_GROWTH_FACTOR = 0.15f; + + // The degree to which the outer ring is scaled in its natural state + private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; + + // The amount of vertical spread between items in the stack [0...1] + private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f; + + // The degree to which the item in the back of the stack is scaled [0...1] + // (0 means it's not scaled at all, 1 means it's scaled to nothing) + private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; + + public static Drawable sSharedFolderLeaveBehind = null; + + private ImageView mPreviewBackground; + private BubbleTextView mFolderName; + + FolderRingAnimator mFolderRingAnimator = null; + + // These variables are all associated with the drawing of the preview; they are stored + // as member variables for shared usage and to avoid computation on each frame + private int mIntrinsicIconSize; + private float mBaselineIconScale; + private int mBaselineIconSize; + private int mAvailableSpaceInPreview; + private int mTotalWidth = -1; + private int mPreviewOffsetX; + private int mPreviewOffsetY; + private float mMaxPerspectiveShift; + boolean mAnimating = false; + private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0); + private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0); + + public FolderIcon(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FolderIcon(Context context) { + super(context); + init(); + } + + private void init() { + mLongPressHelper = new CheckLongPressHelper(this); + } + + public boolean isDropEnabled() { + final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); + final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); + final Workspace workspace = (Workspace) cellLayout.getParent(); + return !workspace.isSmall(); + } + + static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, + FolderInfo folderInfo, IconCache iconCache) { + @SuppressWarnings("all") // suppress dead code warning + final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION; + if (error) { + throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " + + "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " + + "is dependent on this"); + } + + FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); + + icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); + icon.mFolderName.setText(folderInfo.title); + icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); + + icon.setTag(folderInfo); + icon.setOnClickListener(launcher); + icon.mInfo = folderInfo; + icon.mLauncher = launcher; + icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format), + folderInfo.title)); + Folder folder = Folder.fromXml(launcher); + folder.setDragController(launcher.getDragController()); + folder.setFolderIcon(icon); + folder.bind(folderInfo); + icon.mFolder = folder; + + icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); + folderInfo.addListener(icon); + + return icon; + } + + @Override + protected Parcelable onSaveInstanceState() { + sStaticValuesDirty = true; + return super.onSaveInstanceState(); + } + + public static class FolderRingAnimator { + public int mCellX; + public int mCellY; + private CellLayout mCellLayout; + public float mOuterRingSize; + public float mInnerRingSize; + public FolderIcon mFolderIcon = null; + public Drawable mOuterRingDrawable = null; + public Drawable mInnerRingDrawable = null; + public static Drawable sSharedOuterRingDrawable = null; + public static Drawable sSharedInnerRingDrawable = null; + public static int sPreviewSize = -1; + public static int sPreviewPadding = -1; + + private ValueAnimator mAcceptAnimator; + private ValueAnimator mNeutralAnimator; + + public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { + mFolderIcon = folderIcon; + Resources res = launcher.getResources(); + mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); + mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo); + + // We need to reload the static values when configuration changes in case they are + // different in another configuration + if (sStaticValuesDirty) { + sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size); + sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); + sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); + sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo); + sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); + sStaticValuesDirty = false; + } + } + + public void animateToAcceptState() { + if (mNeutralAnimator != null) { + mNeutralAnimator.cancel(); + } + mAcceptAnimator = ValueAnimator.ofFloat(0f, 1f); + mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); + + final int previewSize = sPreviewSize; + mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; + mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; + if (mCellLayout != null) { + mCellLayout.invalidate(); + } + } + }); + mAcceptAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (mFolderIcon != null) { + mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); + } + } + }); + mAcceptAnimator.start(); + } + + public void animateToNaturalState() { + if (mAcceptAnimator != null) { + mAcceptAnimator.cancel(); + } + mNeutralAnimator = ValueAnimator.ofFloat(0f, 1f); + mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); + + final int previewSize = sPreviewSize; + mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; + mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; + if (mCellLayout != null) { + mCellLayout.invalidate(); + } + } + }); + mNeutralAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mCellLayout != null) { + mCellLayout.hideFolderAccept(FolderRingAnimator.this); + } + if (mFolderIcon != null) { + mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); + } + } + }); + mNeutralAnimator.start(); + } + + // Location is expressed in window coordinates + public void getCell(int[] loc) { + loc[0] = mCellX; + loc[1] = mCellY; + } + + // Location is expressed in window coordinates + public void setCell(int x, int y) { + mCellX = x; + mCellY = y; + } + + public void setCellLayout(CellLayout layout) { + mCellLayout = layout; + } + + public float getOuterRingSize() { + return mOuterRingSize; + } + + public float getInnerRingSize() { + return mInnerRingSize; + } + } + + private boolean willAcceptItem(ItemInfo item) { + final int itemType = item.itemType; + return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || + itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && + !mFolder.isFull() && item != mInfo && !mInfo.opened); + } + + public boolean acceptDrop(Object dragInfo) { + final ItemInfo item = (ItemInfo) dragInfo; + return willAcceptItem(item); + } + + public void addItem(ShortcutInfo item) { + mInfo.add(item); + LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); + } + + public void onDragEnter(Object dragInfo) { + if (!willAcceptItem((ItemInfo) dragInfo)) return; + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); + CellLayout layout = (CellLayout) getParent().getParent(); + mFolderRingAnimator.setCell(lp.cellX, lp.cellY); + mFolderRingAnimator.setCellLayout(layout); + mFolderRingAnimator.animateToAcceptState(); + layout.showFolderAccept(mFolderRingAnimator); + } + + public void onDragOver(Object dragInfo) { + } + + public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, + final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, + float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { + + Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1]; + computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), destView.getMeasuredWidth()); + + // This will animate the dragView (srcView) into the new folder + onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); + + // This will animate the first item from it's position as an icon into its + // position as the first item in the preview + animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION); + addItem(destInfo); + } + + public void onDragExit(Object dragInfo) { + onDragExit(); + } + + public void onDragExit() { + mFolderRingAnimator.animateToNaturalState(); + } + + private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, + float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, + DragObject d) { + item.cellX = -1; + item.cellY = -1; + + // Typically, the animateView corresponds to the DragView; however, if this is being done + // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we + // will not have a view to animate + if (animateView != null) { + DragLayer dragLayer = mLauncher.getDragLayer(); + Rect from = new Rect(); + dragLayer.getViewRectRelativeToSelf(animateView, from); + Rect to = finalRect; + if (to == null) { + to = new Rect(); + Workspace workspace = mLauncher.getWorkspace(); + // Set cellLayout and this to it's final state to compute final animation locations + workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); + float scaleX = getScaleX(); + float scaleY = getScaleY(); + setScaleX(1.0f); + setScaleY(1.0f); + scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to); + // Finished computing final animation locations, restore current state + setScaleX(scaleX); + setScaleY(scaleY); + workspace.resetTransitionTransform((CellLayout) getParent().getParent()); + } + + int[] center = new int[2]; + float scale = getLocalCenterForIndex(index, center); + center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); + center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); + + to.offset(center[0] - animateView.getMeasuredWidth() / 2, + center[1] - animateView.getMeasuredHeight() / 2); + + float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f; + + float finalScale = scale * scaleRelativeToDragLayer; + dragLayer.animateView(animateView, from, to, finalAlpha, + 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, + new DecelerateInterpolator(2), new AccelerateInterpolator(2), + postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); + postDelayed(new Runnable() { + public void run() { + addItem(item); + } + }, DROP_IN_ANIMATION_DURATION); + } else { + addItem(item); + } + } + + public void onDrop(DragObject d) { + ShortcutInfo item; + if (d.dragInfo instanceof ApplicationInfo) { + // Came from all apps -- make a copy + item = ((ApplicationInfo) d.dragInfo).makeShortcut(); + } else { + item = (ShortcutInfo) d.dragInfo; + } + mFolder.notifyDrop(); + onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); + } + + public DropTarget getDropTargetDelegate(DragObject d) { + return null; + } + + private void computePreviewDrawingParams(int drawableSize, int totalSize) { + if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { + mIntrinsicIconSize = drawableSize; + mTotalWidth = totalSize; + + final int previewSize = FolderRingAnimator.sPreviewSize; + final int previewPadding = FolderRingAnimator.sPreviewPadding; + + mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); + // cos(45) = 0.707 + ~= 0.1) = 0.8f + int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); + + int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); + mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); + + mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale); + mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; + + mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; + mPreviewOffsetY = previewPadding; + } + } + + private void computePreviewDrawingParams(Drawable d) { + computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); + } + + class PreviewItemDrawingParams { + PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) { + this.transX = transX; + this.transY = transY; + this.scale = scale; + this.overlayAlpha = overlayAlpha; + } + float transX; + float transY; + float scale; + int overlayAlpha; + Drawable drawable; + } + + private float getLocalCenterForIndex(int index, int[] center) { + mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams); + + mParams.transX += mPreviewOffsetX; + mParams.transY += mPreviewOffsetY; + float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; + float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; + + center[0] = (int) Math.round(offsetX); + center[1] = (int) Math.round(offsetY); + return mParams.scale; + } + + private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, + PreviewItemDrawingParams params) { + index = NUM_ITEMS_IN_PREVIEW - index - 1; + float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); + float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); + + float offset = (1 - r) * mMaxPerspectiveShift; + float scaledSize = scale * mBaselineIconSize; + float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; + + // We want to imagine our coordinates from the bottom left, growing up and to the + // right. This is natural for the x-axis, but for the y-axis, we have to invert things. + float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection); + float transX = offset + scaleOffsetCorrection; + float totalScale = mBaselineIconScale * scale; + final int overlayAlpha = (int) (80 * (1 - r)); + + if (params == null) { + params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); + } else { + params.transX = transX; + params.transY = transY; + params.scale = totalScale; + params.overlayAlpha = overlayAlpha; + } + return params; + } + + private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { + canvas.save(); + canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY); + canvas.scale(params.scale, params.scale); + Drawable d = params.drawable; + + if (d != null) { + d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); + d.setFilterBitmap(true); + d.setColorFilter(Color.argb(params.overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP); + d.draw(canvas); + d.clearColorFilter(); + d.setFilterBitmap(false); + } + canvas.restore(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + if (mFolder == null) return; + if (mFolder.getItemCount() == 0 && !mAnimating) return; + + ArrayList items = mFolder.getItemsInReadingOrder(false); + Drawable d; + TextView v; + + // Update our drawing parameters if necessary + if (mAnimating) { + computePreviewDrawingParams(mAnimParams.drawable); + } else { + v = (TextView) items.get(0); + d = v.getCompoundDrawables()[1]; + computePreviewDrawingParams(d); + } + + int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); + if (!mAnimating) { + for (int i = nItemsInPreview - 1; i >= 0; i--) { + v = (TextView) items.get(i); + d = v.getCompoundDrawables()[1]; + + mParams = computePreviewItemDrawingParams(i, mParams); + mParams.drawable = d; + drawPreviewItem(canvas, mParams); + } + } else { + drawPreviewItem(canvas, mAnimParams); + } + } + + private void animateFirstItem(final Drawable d, int duration) { + computePreviewDrawingParams(d); + final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); + + final float scale0 = 1.0f; + final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2; + final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2; + mAnimParams.drawable = d; + + ValueAnimator va = ValueAnimator.ofFloat(0f, 1.0f); + va.addUpdateListener(new AnimatorUpdateListener(){ + public void onAnimationUpdate(ValueAnimator animation) { + float progress = (Float) animation.getAnimatedValue(); + + mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); + mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); + mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); + invalidate(); + } + }); + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mAnimating = true; + } + @Override + public void onAnimationEnd(Animator animation) { + mAnimating = false; + } + }); + va.setDuration(duration); + va.start(); + } + + public void setTextVisible(boolean visible) { + if (visible) { + mFolderName.setVisibility(VISIBLE); + } else { + mFolderName.setVisibility(INVISIBLE); + } + } + + public boolean getTextVisible() { + return mFolderName.getVisibility() == VISIBLE; + } + + public void onItemsChanged() { + invalidate(); + requestLayout(); + } + + public void onAdd(ShortcutInfo item) { + invalidate(); + requestLayout(); + } + + public void onRemove(ShortcutInfo item) { + invalidate(); + requestLayout(); + } + + public void onTitleChanged(CharSequence title) { + mFolderName.setText(title.toString()); + setContentDescription(String.format(getContext().getString(R.string.folder_name_format), + title)); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Call the superclass onTouchEvent first, because sometimes it changes the state to + // isPressed() on an ACTION_UP + boolean result = super.onTouchEvent(event); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLongPressHelper.postCheckForLongPress(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mLongPressHelper.cancelLongPress(); + break; + } + return result; + } + + @Override + public void cancelLongPress() { + super.cancelLongPress(); + + mLongPressHelper.cancelLongPress(); + } +} diff --git a/src/com/cyanogenmod/trebuchet/FolderInfo.java b/src/com/cyanogenmod/trebuchet/FolderInfo.java new file mode 100644 index 000000000..dd896fd34 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/FolderInfo.java @@ -0,0 +1,116 @@ +/* + * 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.cyanogenmod.trebuchet; + +import java.util.ArrayList; + +import android.content.ContentValues; + +/** + * Represents a folder containing shortcuts or apps. + */ +class FolderInfo extends ItemInfo { + + /** + * Whether this folder has been opened + */ + boolean opened; + + /** + * The folder name. + */ + CharSequence title; + + /** + * The apps and shortcuts + */ + ArrayList contents = new ArrayList(); + + ArrayList listeners = new ArrayList(); + + FolderInfo() { + itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER; + } + + /** + * Add an app or shortcut + * + * @param item + */ + public void add(ShortcutInfo item) { + contents.add(item); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onAdd(item); + } + itemsChanged(); + } + + /** + * Remove an app or shortcut. Does not change the DB. + * + * @param item + */ + public void remove(ShortcutInfo item) { + contents.remove(item); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onRemove(item); + } + itemsChanged(); + } + + public void setTitle(CharSequence title) { + this.title = title; + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onTitleChanged(title); + } + } + + @Override + void onAddToDatabase(ContentValues values) { + super.onAddToDatabase(values); + values.put(LauncherSettings.Favorites.TITLE, title.toString()); + } + + void addListener(FolderListener listener) { + listeners.add(listener); + } + + void removeListener(FolderListener listener) { + if (listeners.contains(listener)) { + listeners.remove(listener); + } + } + + void itemsChanged() { + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onItemsChanged(); + } + } + + @Override + void unbind() { + super.unbind(); + listeners.clear(); + } + + interface FolderListener { + public void onAdd(ShortcutInfo item); + public void onRemove(ShortcutInfo item); + public void onTitleChanged(CharSequence title); + public void onItemsChanged(); + } +} diff --git a/src/com/cyanogenmod/trebuchet/HandleView.java b/src/com/cyanogenmod/trebuchet/HandleView.java new file mode 100644 index 000000000..894488855 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/HandleView.java @@ -0,0 +1,76 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; + +import com.cyanogenmod.trebuchet.R; + +public class HandleView extends ImageView { + private static final int ORIENTATION_HORIZONTAL = 1; + + private Launcher mLauncher; + private int mOrientation = ORIENTATION_HORIZONTAL; + + public HandleView(Context context) { + super(context); + } + + public HandleView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public HandleView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HandleView, defStyle, 0); + mOrientation = a.getInt(R.styleable.HandleView_direction, ORIENTATION_HORIZONTAL); + a.recycle(); + + setContentDescription(context.getString(R.string.all_apps_button_label)); + } + + @Override + public View focusSearch(int direction) { + View newFocus = super.focusSearch(direction); + if (newFocus == null && !mLauncher.isAllAppsVisible()) { + final Workspace workspace = mLauncher.getWorkspace(); + workspace.dispatchUnhandledMove(null, direction); + return (mOrientation == ORIENTATION_HORIZONTAL && direction == FOCUS_DOWN) ? + this : workspace; + } + return newFocus; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && mLauncher.isAllAppsVisible()) { + return false; + } + return super.onTouchEvent(ev); + } + + void setLauncher(Launcher launcher) { + mLauncher = launcher; + } +} diff --git a/src/com/cyanogenmod/trebuchet/HolographicImageView.java b/src/com/cyanogenmod/trebuchet/HolographicImageView.java new file mode 100644 index 000000000..d159358f0 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/HolographicImageView.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.ImageView; + +public class HolographicImageView extends ImageView { + + private final HolographicViewHelper mHolographicHelper; + + public HolographicImageView(Context context) { + this(context, null); + } + + public HolographicImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public HolographicImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mHolographicHelper = new HolographicViewHelper(context); + } + + void invalidatePressedFocusedStates() { + mHolographicHelper.invalidatePressedFocusedStates(this); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // One time call to generate the pressed/focused state -- must be called after + // measure/layout + mHolographicHelper.generatePressedFocusedStates(this); + } +} diff --git a/src/com/cyanogenmod/trebuchet/HolographicLinearLayout.java b/src/com/cyanogenmod/trebuchet/HolographicLinearLayout.java new file mode 100644 index 000000000..3af541511 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/HolographicLinearLayout.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.cyanogenmod.trebuchet.R; + +public class HolographicLinearLayout extends LinearLayout { + + private final HolographicViewHelper mHolographicHelper; + private ImageView mImageView; + private int mImageViewId; + + public HolographicLinearLayout(Context context) { + this(context, null); + } + + public HolographicLinearLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public HolographicLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HolographicLinearLayout, + defStyle, 0); + mImageViewId = a.getResourceId(R.styleable.HolographicLinearLayout_sourceImageViewId, -1); + a.recycle(); + + setWillNotDraw(false); + mHolographicHelper = new HolographicViewHelper(context); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if (mImageView != null) { + Drawable d = mImageView.getDrawable(); + if (d instanceof StateListDrawable) { + StateListDrawable sld = (StateListDrawable) d; + sld.setState(getDrawableState()); + } + } + } + + void invalidatePressedFocusedStates() { + mHolographicHelper.invalidatePressedFocusedStates(mImageView); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // One time call to generate the pressed/focused state -- must be called after + // measure/layout + if (mImageView == null) { + mImageView = (ImageView) findViewById(mImageViewId); + } + mHolographicHelper.generatePressedFocusedStates(mImageView); + } +} diff --git a/src/com/cyanogenmod/trebuchet/HolographicOutlineHelper.java b/src/com/cyanogenmod/trebuchet/HolographicOutlineHelper.java new file mode 100644 index 000000000..e2d2eaaca --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/HolographicOutlineHelper.java @@ -0,0 +1,218 @@ +/* + * 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.cyanogenmod.trebuchet; + +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(); + private final Paint mAlphaClipPaint = new Paint(); + + public static final int MAX_OUTER_BLUR_RADIUS; + public static final int MIN_OUTER_BLUR_RADIUS; + + private static final BlurMaskFilter sExtraThickOuterBlurMaskFilter; + private static final BlurMaskFilter sThickOuterBlurMaskFilter; + private static final BlurMaskFilter sMediumOuterBlurMaskFilter; + private static final BlurMaskFilter sThinOuterBlurMaskFilter; + private static final BlurMaskFilter sThickInnerBlurMaskFilter; + private static final BlurMaskFilter sExtraThickInnerBlurMaskFilter; + private static final BlurMaskFilter sMediumInnerBlurMaskFilter; + + private static final int THICK = 0; + private static final int MEDIUM = 1; + private static final int EXTRA_THICK = 2; + + static { + final float scale = LauncherApplication.getScreenDensity(); + + MIN_OUTER_BLUR_RADIUS = (int) (scale * 1.0f); + MAX_OUTER_BLUR_RADIUS = (int) (scale * 12.0f); + + sExtraThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 12.0f, BlurMaskFilter.Blur.OUTER); + sThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.OUTER); + sMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER); + sThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER); + sExtraThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.NORMAL); + sThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL); + sMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL); + } + + 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); + MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(180, 255); + mAlphaClipPaint.setMaskFilter(alphaClipTable); + } + + /** + * Returns the interpolated holographic highlight alpha for the effect we want when scrolling + * pages. + */ + public static float highlightAlphaInterpolator(float r) { + float maxAlpha = 0.6f; + return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f); + } + + /** + * Returns the interpolated view alpha for the effect we want when scrolling pages. + */ + public static float viewAlphaInterpolator(float r) { + final float pivot = 0.95f; + if (r < pivot) { + return (float) Math.pow(r / pivot, 1.5f); + } else { + return 1.0f; + } + } + + /** + * 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, int thickness) { + applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, mAlphaClipPaint, + thickness); + } + void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, + int outlineColor, Paint alphaClipPaint, int thickness) { + + // We start by removing most of the alpha channel so as to ignore shadows, and + // other types of partial transparency when defining the shape of the object + if (alphaClipPaint == null) { + alphaClipPaint = mAlphaClipPaint; + } + Bitmap glowShape = srcDst.extractAlpha(alphaClipPaint, mTempOffset); + + // calculate the outer blur first + BlurMaskFilter outerBlurMaskFilter; + switch (thickness) { + case EXTRA_THICK: + outerBlurMaskFilter = sExtraThickOuterBlurMaskFilter; + break; + case THICK: + outerBlurMaskFilter = sThickOuterBlurMaskFilter; + break; + case MEDIUM: + outerBlurMaskFilter = sMediumOuterBlurMaskFilter; + break; + default: + throw new RuntimeException("Invalid blur thickness"); + } + mBlurPaint.setMaskFilter(outerBlurMaskFilter); + int[] outerBlurOffset = new int[2]; + Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset); + if (thickness == EXTRA_THICK) { + mBlurPaint.setMaskFilter(sMediumOuterBlurMaskFilter); + } else { + mBlurPaint.setMaskFilter(sThinOuterBlurMaskFilter); + } + + int[] brightOutlineOffset = new int[2]; + Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset); + + // calculate the inner blur + srcDstCanvas.setBitmap(glowShape); + srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); + BlurMaskFilter innerBlurMaskFilter; + switch (thickness) { + case EXTRA_THICK: + innerBlurMaskFilter = sExtraThickInnerBlurMaskFilter; + break; + case THICK: + innerBlurMaskFilter = sThickInnerBlurMaskFilter; + break; + case MEDIUM: + innerBlurMaskFilter = sMediumInnerBlurMaskFilter; + break; + default: + throw new RuntimeException("Invalid blur thickness"); + } + mBlurPaint.setMaskFilter(innerBlurMaskFilter); + int[] thickInnerBlurOffset = new int[2]; + Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset); + + // mask out the inner blur + srcDstCanvas.setBitmap(thickInnerBlur); + srcDstCanvas.drawBitmap(glowShape, -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(0, 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(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], + mHolographicPaint); + + // cleanup + srcDstCanvas.setBitmap(null); + brightOutline.recycle(); + thickOuterBlur.recycle(); + thickInnerBlur.recycle(); + glowShape.recycle(); + } + + void applyExtraThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, + int outlineColor) { + applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, EXTRA_THICK); + } + + void applyThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, + int outlineColor) { + applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, THICK); + } + + void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, + int outlineColor, Paint alphaClipPaint) { + applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, alphaClipPaint, + MEDIUM); + } + + void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, + int outlineColor) { + applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, MEDIUM); + } + +} diff --git a/src/com/cyanogenmod/trebuchet/HolographicViewHelper.java b/src/com/cyanogenmod/trebuchet/HolographicViewHelper.java new file mode 100644 index 000000000..068a8f5a7 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/HolographicViewHelper.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.drawable.StateListDrawable; +import android.widget.ImageView; + +public class HolographicViewHelper { + + private final Canvas mTempCanvas = new Canvas(); + + private boolean mStatesUpdated; + private int mHighlightColor; + + public HolographicViewHelper(Context context) { + Resources res = context.getResources(); + mHighlightColor = res.getColor(android.R.color.holo_blue_light); + } + + /** + * Generate the pressed/focused states if necessary. + */ + void generatePressedFocusedStates(ImageView v) { + if (!mStatesUpdated && v != null) { + mStatesUpdated = true; + Bitmap outline = createPressImage(v, mTempCanvas); + FastBitmapDrawable d = new FastBitmapDrawable(outline); + + StateListDrawable states = new StateListDrawable(); + states.addState(new int[] {android.R.attr.state_pressed}, d); + states.addState(new int[] {android.R.attr.state_focused}, d); + states.addState(new int[] {}, v.getDrawable()); + v.setImageDrawable(states); + } + } + + /** + * Invalidates the pressed/focused states. + */ + void invalidatePressedFocusedStates(ImageView v) { + mStatesUpdated = false; + if (v != null) { + v.invalidate(); + } + } + + /** + * Creates a new press state image which is the old image with a blue overlay. + * Responsibility for the bitmap is transferred to the caller. + */ + private Bitmap createPressImage(ImageView v, Canvas canvas) { + final int padding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; + final Bitmap b = Bitmap.createBitmap( + v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + canvas.save(); + v.getDrawable().draw(canvas); + canvas.restore(); + canvas.drawColor(mHighlightColor, PorterDuff.Mode.SRC_IN); + canvas.setBitmap(null); + + return b; + } +} diff --git a/src/com/cyanogenmod/trebuchet/Hotseat.java b/src/com/cyanogenmod/trebuchet/Hotseat.java new file mode 100644 index 000000000..573be1b83 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Hotseat.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +import com.cyanogenmod.trebuchet.R; + +public class Hotseat extends FrameLayout { + @SuppressWarnings("unused") + private static final String TAG = "Hotseat"; + + private Launcher mLauncher; + private CellLayout mContent; + + private int mCellCountX; + private int mCellCountY; + private int mAllAppsButtonRank; + private boolean mIsLandscape; + + public Hotseat(Context context) { + this(context, null); + } + + public Hotseat(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Hotseat(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.Hotseat, defStyle, 0); + mCellCountX = a.getInt(R.styleable.Hotseat_cellCountX, -1); + mCellCountY = a.getInt(R.styleable.Hotseat_cellCountY, -1); + mAllAppsButtonRank = context.getResources().getInteger(R.integer.hotseat_all_apps_index); + mIsLandscape = context.getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; + } + + public void setup(Launcher launcher) { + mLauncher = launcher; + setOnKeyListener(new HotseatIconKeyEventListener()); + } + + CellLayout getLayout() { + return mContent; + } + + /* Get the orientation invariant order of the item in the hotseat for persistence. */ + int getOrderInHotseat(int x, int y) { + return mIsLandscape ? (mContent.getCountY() - y - 1) : x; + } + /* Get the orientation specific coordinates given an invariant order in the hotseat. */ + int getCellXFromOrder(int rank) { + return mIsLandscape ? 0 : rank; + } + int getCellYFromOrder(int rank) { + return mIsLandscape ? (mContent.getCountY() - (rank + 1)) : 0; + } + public boolean isAllAppsButtonRank(int rank) { + return rank == mAllAppsButtonRank; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (mCellCountX < 0) mCellCountX = LauncherModel.getCellCountX(); + if (mCellCountY < 0) mCellCountY = LauncherModel.getCellCountY(); + mContent = (CellLayout) findViewById(R.id.layout); + mContent.setGridSize(mCellCountX, mCellCountY); + mContent.setIsHotseat(true); + + resetLayout(); + } + + void resetLayout() { + mContent.removeAllViewsInLayout(); + + // Add the Apps button + Context context = getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + BubbleTextView allAppsButton = (BubbleTextView) + inflater.inflate(R.layout.application, mContent, false); + allAppsButton.setCompoundDrawablesWithIntrinsicBounds(null, + context.getResources().getDrawable(R.drawable.all_apps_button_icon), null, null); + allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label)); + allAppsButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mLauncher != null && + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + mLauncher.onTouchDownAllAppsButton(v); + } + return false; + } + }); + + allAppsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(android.view.View v) { + if (mLauncher != null) { + mLauncher.onClickAllAppsButton(v); + } + } + }); + + // Note: We do this to ensure that the hotseat is always laid out in the orientation of + // the hotseat in order regardless of which orientation they were added + int x = getCellXFromOrder(mAllAppsButtonRank); + int y = getCellYFromOrder(mAllAppsButtonRank); + CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1); + lp.canReorder = false; + mContent.addViewToCellLayout(allAppsButton, -1, 0, lp, true); + } +} diff --git a/src/com/cyanogenmod/trebuchet/IconCache.java b/src/com/cyanogenmod/trebuchet/IconCache.java new file mode 100644 index 000000000..15b49ba77 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/IconCache.java @@ -0,0 +1,229 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +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 java.util.HashMap; + +/** + * Cache of application icons. Icons can be made from any thread. + */ +public class IconCache { + @SuppressWarnings("unused") + private static final String TAG = "Launcher.IconCache"; + + private static final int INITIAL_ICON_CACHE_CAPACITY = 50; + + private static class CacheEntry { + public Bitmap icon; + public String title; + } + + private final Bitmap mDefaultIcon; + private final LauncherApplication mContext; + private final PackageManager mPackageManager; + private final HashMap mCache = + new HashMap(INITIAL_ICON_CACHE_CAPACITY); + private int mIconDpi; + + public IconCache(LauncherApplication context) { + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + mContext = context; + mPackageManager = context.getPackageManager(); + mIconDpi = activityManager.getLauncherLargeIconDensity(); + + // need to set mIconDpi before getting default icon + mDefaultIcon = makeDefaultIcon(); + } + + public Drawable getFullResDefaultActivityIcon() { + return getFullResIcon(Resources.getSystem(), + android.R.mipmap.sym_def_app_icon); + } + + public Drawable getFullResIcon(Resources resources, int iconId) { + Drawable d; + try { + d = resources.getDrawableForDensity(iconId, mIconDpi); + } catch (Resources.NotFoundException e) { + d = null; + } + + return (d != null) ? d : getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(String packageName, int iconId) { + Resources resources; + try { + resources = mPackageManager.getResourcesForApplication(packageName); + } catch (PackageManager.NameNotFoundException e) { + resources = null; + } + if (resources != null) { + if (iconId != 0) { + return getFullResIcon(resources, iconId); + } + } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(ResolveInfo info) { + return getFullResIcon(info.activityInfo); + } + + public Drawable getFullResIcon(ActivityInfo info) { + + Resources resources; + try { + resources = mPackageManager.getResourcesForApplication( + info.applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + resources = null; + } + if (resources != null) { + int iconId = info.getIconResource(); + if (iconId != 0) { + return getFullResIcon(resources, iconId); + } + } + return getFullResDefaultActivityIcon(); + } + + private Bitmap makeDefaultIcon() { + Drawable d = getFullResDefaultActivityIcon(); + Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1), + Math.max(d.getIntrinsicHeight(), 1), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + d.setBounds(0, 0, b.getWidth(), b.getHeight()); + d.draw(c); + c.setBitmap(null); + return b; + } + + /** + * Remove any records for the supplied ComponentName. + */ + public void remove(ComponentName componentName) { + synchronized (mCache) { + mCache.remove(componentName); + } + } + + /** + * Empty out the cache. + */ + public void flush() { + synchronized (mCache) { + mCache.clear(); + } + } + + /** + * Fill in "application" with the icon and label for "info." + */ + public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info, + HashMap labelCache) { + synchronized (mCache) { + CacheEntry entry = cacheLocked(application.componentName, info, labelCache); + + application.title = entry.title; + application.iconBitmap = entry.icon; + } + } + + public Bitmap getIcon(Intent intent) { + synchronized (mCache) { + final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0); + ComponentName component = intent.getComponent(); + + if (resolveInfo == null || component == null) { + return mDefaultIcon; + } + + CacheEntry entry = cacheLocked(component, resolveInfo, null); + return entry.icon; + } + } + + public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo, + HashMap labelCache) { + synchronized (mCache) { + if (resolveInfo == null || component == null) { + return null; + } + + CacheEntry entry = cacheLocked(component, resolveInfo, labelCache); + return entry.icon; + } + } + + public boolean isDefaultIcon(Bitmap icon) { + return mDefaultIcon == icon; + } + + private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info, + HashMap labelCache) { + CacheEntry entry = mCache.get(componentName); + if (entry == null) { + entry = new CacheEntry(); + + mCache.put(componentName, entry); + + ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info); + if (labelCache != null && labelCache.containsKey(key)) { + entry.title = labelCache.get(key).toString(); + } else { + entry.title = info.loadLabel(mPackageManager).toString(); + if (labelCache != null) { + labelCache.put(key, entry.title); + } + } + if (entry.title == null) { + entry.title = info.activityInfo.name; + } + + entry.icon = Utilities.createIconBitmap( + getFullResIcon(info), mContext); + } + return entry; + } + + public HashMap getAllIcons() { + synchronized (mCache) { + HashMap set = new HashMap(); + for (ComponentName cn : mCache.keySet()) { + final CacheEntry e = mCache.get(cn); + set.put(cn, e.icon); + } + return set; + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/InfoDropTarget.java b/src/com/cyanogenmod/trebuchet/InfoDropTarget.java new file mode 100644 index 000000000..c24bf8b3e --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/InfoDropTarget.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.ComponentName; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.TransitionDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.cyanogenmod.trebuchet.R; + +public class InfoDropTarget extends ButtonDropTarget { + + private ColorStateList mOriginalTextColor; + private TransitionDrawable mDrawable; + + public InfoDropTarget(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public InfoDropTarget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mOriginalTextColor = getTextColors(); + + // Get the hover color + Resources r = getResources(); + mHoverColor = r.getColor(R.color.info_target_hover_tint); + mDrawable = (TransitionDrawable) getCurrentDrawable(); + mDrawable.setCrossFadeEnabled(true); + + // Remove the text in the Phone UI in landscape + int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + if (!LauncherApplication.isScreenLarge()) { + setText(""); + } + } + } + + private boolean isFromAllApps(DragSource source) { + return (source instanceof AppsCustomizePagedView); + } + + @Override + public boolean acceptDrop(DragObject d) { + // 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 drag source. + ComponentName componentName = null; + if (d.dragInfo instanceof ApplicationInfo) { + componentName = ((ApplicationInfo) d.dragInfo).componentName; + } else if (d.dragInfo instanceof ShortcutInfo) { + componentName = ((ShortcutInfo) d.dragInfo).intent.getComponent(); + } else if (d.dragInfo instanceof PendingAddItemInfo) { + componentName = ((PendingAddItemInfo) d.dragInfo).componentName; + } + if (componentName != null) { + mLauncher.startApplicationDetailsActivity(componentName); + } + + // There is no post-drop animation, so clean up the DragView now + d.deferDragViewCleanupPostAnimation = false; + return false; + } + + @Override + public void onDragStart(DragSource source, Object info, int dragAction) { + boolean isVisible = true; + + // Hide this button unless we are dragging something from AllApps + if (!isFromAllApps(source)) { + isVisible = false; + } + + mActive = isVisible; + mDrawable.resetTransition(); + setTextColor(mOriginalTextColor); + ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE); + } + + @Override + public void onDragEnd() { + super.onDragEnd(); + mActive = false; + } + + public void onDragEnter(DragObject d) { + super.onDragEnter(d); + + mDrawable.startTransition(mTransitionDuration); + setTextColor(mHoverColor); + } + + public void onDragExit(DragObject d) { + super.onDragExit(d); + + if (!d.dragComplete) { + mDrawable.resetTransition(); + setTextColor(mOriginalTextColor); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/InstallShortcutReceiver.java b/src/com/cyanogenmod/trebuchet/InstallShortcutReceiver.java new file mode 100644 index 000000000..d40ceba58 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/InstallShortcutReceiver.java @@ -0,0 +1,252 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.os.Debug; +import android.widget.Toast; + +import com.cyanogenmod.trebuchet.R; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class InstallShortcutReceiver extends BroadcastReceiver { + public static final String ACTION_INSTALL_SHORTCUT = + "com.cyanogenmod.trebuchet.action.INSTALL_SHORTCUT"; + public static final String NEW_APPS_PAGE_KEY = "apps.new.page"; + public static final String NEW_APPS_LIST_KEY = "apps.new.list"; + + public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; + public static final int NEW_SHORTCUT_STAGGER_DELAY = 75; + + private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0; + private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1; + private static final int INSTALL_SHORTCUT_NO_SPACE = -2; + + // A mime-type representing shortcut data + public static final String SHORTCUT_MIMETYPE = + "com.cyanogenmod.trebuchet/shortcut"; + + // The set of shortcuts that are pending install + private static ArrayList mInstallQueue = + new ArrayList(); + + // Determines whether to defer installing shortcuts immediately until + // processAllPendingInstalls() is called. + private static boolean mUseInstallQueue = false; + + private static class PendingInstallShortcutInfo { + Intent data; + Intent launchIntent; + String name; + + public PendingInstallShortcutInfo(Intent rawData, String shortcutName, + Intent shortcutIntent) { + data = rawData; + name = shortcutName; + launchIntent = shortcutIntent; + } + } + + public void onReceive(Context context, Intent data) { + if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { + return; + } + + Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + if (intent == null) { + return; + } + // This name is only used for comparisons and notifications, so fall back to activity name + // if not supplied + String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + if (name == null) { + try { + PackageManager pm = context.getPackageManager(); + ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0); + name = info.loadLabel(pm).toString(); + } catch (PackageManager.NameNotFoundException nnfe) { + return; + } + } + // Queue the item up for adding if launcher has not loaded properly yet + boolean launcherNotLoaded = LauncherModel.getCellCountX() <= 0 || + LauncherModel.getCellCountY() <= 0; + + PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent); + if (mUseInstallQueue || launcherNotLoaded) { + mInstallQueue.add(info); + } else { + processInstallShortcut(context, info); + } + } + + static void enableInstallQueue() { + mUseInstallQueue = true; + } + static void disableAndFlushInstallQueue(Context context) { + mUseInstallQueue = false; + flushInstallQueue(context); + } + static void flushInstallQueue(Context context) { + Iterator iter = mInstallQueue.iterator(); + while (iter.hasNext()) { + processInstallShortcut(context, iter.next()); + iter.remove(); + } + } + + private static void processInstallShortcut(Context context, + PendingInstallShortcutInfo pendingInfo) { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); + + final Intent data = pendingInfo.data; + final Intent intent = pendingInfo.launchIntent; + final String name = pendingInfo.name; + + // Lock on the app so that we don't try and get the items while apps are being added + LauncherApplication app = (LauncherApplication) context.getApplicationContext(); + final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL}; + boolean found = false; + synchronized (app) { + final ArrayList items = LauncherModel.getItemsInLocalCoordinates(context); + final boolean exists = LauncherModel.shortcutExists(context, name, intent); + + // Try adding to the workspace screens incrementally, starting at the default or center + // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1)) + final int screen = Launcher.DEFAULT_SCREEN; + for (int i = 0; i < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) { + int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1); + if (0 <= si && si < Launcher.SCREEN_COUNT) { + found = installShortcut(context, data, items, name, intent, si, exists, sp, + result); + } + } + } + + // We only report error messages (duplicate shortcut or out of space) as the add-animation + // will provide feedback otherwise + if (!found) { + if (result[0] == INSTALL_SHORTCUT_NO_SPACE) { + Toast.makeText(context, context.getString(R.string.completely_out_of_space), + Toast.LENGTH_SHORT).show(); + } else if (result[0] == INSTALL_SHORTCUT_IS_DUPLICATE) { + Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name), + Toast.LENGTH_SHORT).show(); + } + } + } + + private static boolean installShortcut(Context context, Intent data, ArrayList items, + String name, Intent intent, final int screen, boolean shortcutExists, + final SharedPreferences sharedPrefs, int[] result) { + int[] tmpCoordinates = new int[2]; + if (findEmptyCell(context, items, tmpCoordinates, screen)) { + if (intent != null) { + if (intent.getAction() == null) { + intent.setAction(Intent.ACTION_VIEW); + } else if (intent.getAction().equals(Intent.ACTION_MAIN) && + intent.getCategories() != null && + intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } + + // By default, we allow for duplicate entries (located in + // different places) + boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true); + if (duplicate || !shortcutExists) { + // If the new app is going to fall into the same page as before, then just + // continue adding to the current page + int newAppsScreen = sharedPrefs.getInt(NEW_APPS_PAGE_KEY, screen); + Set newApps = new HashSet(); + if (newAppsScreen == screen) { + newApps = sharedPrefs.getStringSet(NEW_APPS_LIST_KEY, newApps); + } + synchronized (newApps) { + newApps.add(intent.toUri(0).toString()); + } + final Set savedNewApps = newApps; + new Thread("setNewAppsThread") { + public void run() { + synchronized (savedNewApps) { + sharedPrefs.edit() + .putInt(NEW_APPS_PAGE_KEY, screen) + .putStringSet(NEW_APPS_LIST_KEY, savedNewApps) + .commit(); + } + } + }.start(); + + // Update the Launcher db + LauncherApplication app = (LauncherApplication) context.getApplicationContext(); + ShortcutInfo info = app.getModel().addShortcut(context, data, + LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, + tmpCoordinates[0], tmpCoordinates[1], true); + if (info == null) { + return false; + } + } else { + result[0] = INSTALL_SHORTCUT_IS_DUPLICATE; + } + + return true; + } + } else { + result[0] = INSTALL_SHORTCUT_NO_SPACE; + } + + return false; + } + + private static boolean findEmptyCell(Context context, ArrayList items, int[] xy, + int screen) { + final int xCount = LauncherModel.getCellCountX(); + final int yCount = LauncherModel.getCellCountY(); + boolean[][] occupied = new boolean[xCount][yCount]; + + ItemInfo item = null; + int cellX, cellY, spanX, spanY; + for (int i = 0; i < items.size(); ++i) { + item = items.get(i); + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (item.screen == screen) { + cellX = item.cellX; + cellY = item.cellY; + spanX = item.spanX; + spanY = item.spanY; + for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) { + for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) { + occupied[x][y] = true; + } + } + } + } + } + + return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied); + } +} diff --git a/src/com/cyanogenmod/trebuchet/InstallWidgetReceiver.java b/src/com/cyanogenmod/trebuchet/InstallWidgetReceiver.java new file mode 100644 index 000000000..a133f51e3 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/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.cyanogenmod.trebuchet; + +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.cyanogenmod.trebuchet.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.cyanogenmod.trebuchet.action.INSTALL_WIDGET"; + public static final String ACTION_SUPPORTS_CLIPDATA_MIMETYPE = + "com.cyanogenmod.trebuchet.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.cyanogenmod.trebuchet.extra.widget.COMPONENT"; + public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE = + "com.cyanogenmod.trebuchet.extra.widget.CONFIGURATION_DATA_MIME_TYPE"; + public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA = + "com.cyanogenmod.trebuchet.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 mActivities; + private CellLayout mTargetLayout; + private int mTargetLayoutScreen; + private int[] mTargetLayoutPos; + + public WidgetListAdapter(Launcher l, String mimeType, ClipData data, + List 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 AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo; + + final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType, + mClipData); + mLauncher.addAppWidgetFromDrop(createInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, + mTargetLayoutScreen, null, null, mTargetLayoutPos); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/InterruptibleInOutAnimator.java b/src/com/cyanogenmod/trebuchet/InterruptibleInOutAnimator.java new file mode 100644 index 000000000..82ec2d8f3 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/InterruptibleInOutAnimator.java @@ -0,0 +1,130 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; + +/** + * 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 AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(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/cyanogenmod/trebuchet/ItemInfo.java b/src/com/cyanogenmod/trebuchet/ItemInfo.java new file mode 100644 index 000000000..c76108fc5 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/ItemInfo.java @@ -0,0 +1,188 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.ContentValues; +import android.content.Intent; +import android.graphics.Bitmap; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Represents an item in the launcher. + */ +class ItemInfo { + + static final int NO_ID = -1; + + /** + * The id in the settings database for this item + */ + long id = NO_ID; + + /** + * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER}, or + * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}. + */ + int itemType; + + /** + * The id of the container that holds this item. For the desktop, this will be + * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it + * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders + * it will be the id of the folder. + */ + long container = NO_ID; + + /** + * Iindicates the screen in which the shortcut appears. + */ + int screen = -1; + + /** + * Indicates the X position of the associated cell. + */ + int cellX = -1; + + /** + * Indicates the Y position of the associated cell. + */ + int cellY = -1; + + /** + * Indicates the X cell span. + */ + int spanX = 1; + + /** + * Indicates the Y cell span. + */ + int spanY = 1; + + /** + * Indicates the minimum X cell span. + */ + int minSpanX = 1; + + /** + * Indicates the minimum Y cell span. + */ + int minSpanY = 1; + /** + * Indicates whether the item is a gesture. + */ + boolean isGesture = false; + + /** + * The position of the item in a drag-and-drop operation. + */ + int[] dropPos = null; + + ItemInfo() { + } + + ItemInfo(ItemInfo info) { + id = info.id; + cellX = info.cellX; + cellY = info.cellY; + spanX = info.spanX; + spanY = info.spanY; + screen = info.screen; + itemType = info.itemType; + container = info.container; + } + + /** Returns the package name that the intent will resolve to, or an empty string if + * none exists. */ + static String getPackageName(Intent intent) { + if (intent != null) { + String packageName = intent.getPackage(); + if (packageName == null && intent.getComponent() != null) { + packageName = intent.getComponent().getPackageName(); + } + if (packageName != null) { + return packageName; + } + } + return ""; + } + + /** + * Write the fields of this item to the DB + * + * @param values + */ + void onAddToDatabase(ContentValues values) { + values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType); + if (!isGesture) { + values.put(LauncherSettings.Favorites.CONTAINER, container); + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, cellX); + values.put(LauncherSettings.Favorites.CELLY, cellY); + values.put(LauncherSettings.Favorites.SPANX, spanX); + values.put(LauncherSettings.Favorites.SPANY, spanY); + } + } + + 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. + int size = bitmap.getWidth() * bitmap.getHeight() * 4; + ByteArrayOutputStream out = new ByteArrayOutputStream(size); + try { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + out.close(); + return out.toByteArray(); + } catch (IOException e) { + Log.w("Favorite", "Could not write icon"); + return null; + } + } + + static void writeBitmap(ContentValues values, Bitmap bitmap) { + if (bitmap != null) { + byte[] data = flattenBitmap(bitmap); + values.put(LauncherSettings.Favorites.ICON, data); + } + } + + /** + * It is very important that sub-classes implement this if they contain any references + * to the activity (anything in the view hierarchy etc.). If not, leaks can result since + * ItemInfo objects persist across rotation and can hence leak by holding stale references + * to the old view hierarchy / activity. + */ + void unbind() { + } + + @Override + public String toString() { + return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container + + " screen=" + screen + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + + " spanY=" + spanY + " isGesture=" + isGesture + " dropPos=" + dropPos + ")"; + } +} diff --git a/src/com/cyanogenmod/trebuchet/Launcher.java b/src/com/cyanogenmod/trebuchet/Launcher.java new file mode 100644 index 000000000..59f5f493d --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Launcher.java @@ -0,0 +1,3710 @@ + +/* + * 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.cyanogenmod.trebuchet; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.SearchManager; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks2; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.StrictMode; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.provider.Settings; +import android.speech.RecognizerIntent; +import android.text.Selection; +import android.text.SpannableStringBuilder; +import android.text.method.TextKeyListener; +import android.util.Log; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.inputmethod.InputMethodManager; +import android.widget.Advanceable; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.common.Search; +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.DropTarget.DragObject; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Default launcher application. + */ +public final class Launcher extends Activity + implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, + AllAppsView.Watcher, View.OnTouchListener { + static final String TAG = "Launcher"; + static final boolean LOGD = false; + + static final boolean PROFILE_STARTUP = false; + static final boolean DEBUG_WIDGETS = false; + static final boolean DEBUG_STRICT_MODE = false; + + private static final int MENU_GROUP_WALLPAPER = 1; + private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1; + private static final int MENU_MANAGE_APPS = MENU_WALLPAPER_SETTINGS + 1; + private static final int MENU_SYSTEM_SETTINGS = MENU_MANAGE_APPS + 1; + private static final int MENU_HELP = MENU_SYSTEM_SETTINGS + 1; + + private static final int REQUEST_CREATE_SHORTCUT = 1; + private static final int REQUEST_CREATE_APPWIDGET = 5; + private static final int REQUEST_PICK_APPLICATION = 6; + private static final int REQUEST_PICK_SHORTCUT = 7; + private static final int REQUEST_PICK_APPWIDGET = 9; + private static final int REQUEST_PICK_WALLPAPER = 10; + + private static final int REQUEST_BIND_APPWIDGET = 11; + + static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; + + static final int SCREEN_COUNT = 5; + static final int DEFAULT_SCREEN = 2; + + private static final String PREFERENCES = "launcher.preferences"; + static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher.force_enable_rotation"; + + // The Intent extra that defines whether to ignore the launch animation + static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = + "com.cyanogenmod.trebuchet.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION"; + + // Type: int + private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; + // Type: int + private static final String RUNTIME_STATE = "launcher.state"; + // Type: int + private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container"; + // Type: int + private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen"; + // Type: int + private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x"; + // Type: int + private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y"; + // 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"; + // Type: int + private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x"; + // Type: int + private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y"; + // Type: parcelable + private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info"; + + private static final String TOOLBAR_ICON_METADATA_NAME = "com.cyanogenmod.trebuchet.toolbar_icon"; + private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME = + "com.cyanogenmod.trebuchet.toolbar_search_icon"; + private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME = + "com.cyanogenmod.trebuchet.toolbar_voice_search_icon"; + + /** The different states that Launcher can be in. */ + private enum State { WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED }; + private State mState = State.WORKSPACE; + private AnimatorSet mStateAnimation; + private AnimatorSet mDividerAnimator; + + static final int APPWIDGET_HOST_ID = 1024; + private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300; + private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600; + private static final int SHOW_CLING_DURATION = 550; + private static final int DISMISS_CLING_DURATION = 250; + + private static final Object sLock = new Object(); + private static int sScreen = DEFAULT_SCREEN; + + // How long to wait before the new-shortcut animation automatically pans the workspace + private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 10; + + private final BroadcastReceiver mCloseSystemDialogsReceiver + = new CloseSystemDialogsIntentReceiver(); + private final ContentObserver mWidgetObserver = new AppWidgetResetObserver(); + + private LayoutInflater mInflater; + + private Workspace mWorkspace; + private View mQsbDivider; + private View mDockDivider; + private DragLayer mDragLayer; + private DragController mDragController; + + private AppWidgetManager mAppWidgetManager; + private LauncherAppWidgetHost mAppWidgetHost; + + private ItemInfo mPendingAddInfo = new ItemInfo(); + private AppWidgetProviderInfo mPendingAddWidgetInfo; + + private int[] mTmpAddItemCellCoordinates = new int[2]; + + private FolderInfo mFolderInfo; + + private Hotseat mHotseat; + private View mAllAppsButton; + + private SearchDropTargetBar mSearchDropTargetBar; + private AppsCustomizeTabHost mAppsCustomizeTabHost; + private AppsCustomizePagedView mAppsCustomizeContent; + private boolean mAutoAdvanceRunning = false; + + private Bundle mSavedState; + + private SpannableStringBuilder mDefaultKeySsb = null; + + private boolean mWorkspaceLoading = true; + + private boolean mPaused = true; + private boolean mRestoring; + private boolean mWaitingForResult; + private boolean mOnResumeNeedsLoad; + + private Bundle mSavedInstanceState; + + private LauncherModel mModel; + private IconCache mIconCache; + private boolean mUserPresent = true; + private boolean mVisible = false; + private boolean mAttached = false; + + private static LocaleConfiguration sLocaleConfiguration = null; + + private static HashMap sFolders = new HashMap(); + + private Intent mAppMarketIntent = null; + + // Related to the auto-advancing of widgets + private final int ADVANCE_MSG = 1; + private final int mAdvanceInterval = 20000; + private final int mAdvanceStagger = 250; + private long mAutoAdvanceSentTime; + private long mAutoAdvanceTimeLeft = -1; + private HashMap mWidgetsToAdvance = + new HashMap(); + + // Determines how long to wait after a rotation before restoring the screen orientation to + // match the sensor state. + private final int mRestoreScreenOrientationDelay = 500; + + // External icons saved in case of resource changes, orientation, etc. + private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2]; + private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2]; + private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2]; + + static final ArrayList sDumpLogs = new ArrayList(); + + // We only want to get the SharedPreferences once since it does an FS stat each time we get + // it from the context. + private SharedPreferences mSharedPrefs; + + // Holds the page that we need to animate to, and the icon views that we need to animate up + // when we scroll to that page on resume. + private int mNewShortcutAnimatePage = -1; + private ArrayList mNewShortcutAnimateViews = new ArrayList(); + private ImageView mFolderIconImageView; + private Bitmap mFolderIconBitmap; + private Canvas mFolderIconCanvas; + private Rect mRectForFolderAnimation = new Rect(); + + private BubbleTextView mWaitingForResume; + + private Runnable mBuildLayersRunnable = new Runnable() { + public void run() { + if (mWorkspace != null) { + mWorkspace.buildPageHardwareLayers(); + } + } + }; + + private static ArrayList sPendingAddList + = new ArrayList(); + + private static class PendingAddArguments { + int requestCode; + Intent intent; + long container; + int screen; + int cellX; + int cellY; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (DEBUG_STRICT_MODE) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectDiskReads() + .detectDiskWrites() + .detectNetwork() // or .detectAll() for all detectable problems + .penaltyLog() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectLeakedSqlLiteObjects() + .detectLeakedClosableObjects() + .penaltyLog() + .penaltyDeath() + .build()); + } + + super.onCreate(savedInstanceState); + LauncherApplication app = ((LauncherApplication)getApplication()); + mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(), + Context.MODE_PRIVATE); + mModel = app.setLauncher(this); + mIconCache = app.getIconCache(); + mDragController = new DragController(this); + mInflater = getLayoutInflater(); + + mAppWidgetManager = AppWidgetManager.getInstance(this); + mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); + mAppWidgetHost.startListening(); + + if (PROFILE_STARTUP) { + android.os.Debug.startMethodTracing( + Environment.getExternalStorageDirectory() + "/launcher"); + } + + checkForLocaleChange(); + setContentView(R.layout.launcher); + setupViews(); + showFirstRunWorkspaceCling(); + + registerContentObservers(); + + lockAllApps(); + + mSavedState = savedInstanceState; + restoreState(mSavedState); + + // Update customization drawer _after_ restoring the states + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.onPackagesUpdated(); + } + + if (PROFILE_STARTUP) { + android.os.Debug.stopMethodTracing(); + } + + if (!mRestoring) { + mModel.startLoader(true); + } + + if (!mModel.isAllAppsLoaded()) { + ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent(); + mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent); + } + + // For handling default keys + mDefaultKeySsb = new SpannableStringBuilder(); + Selection.setSelection(mDefaultKeySsb, 0); + + IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + registerReceiver(mCloseSystemDialogsReceiver, filter); + + updateGlobalIcons(); + + // On large interfaces, we want the screen to auto-rotate based on the current orientation + unlockScreenOrientation(true); + } + + private void updateGlobalIcons() { + boolean searchVisible = false; + boolean voiceVisible = false; + // If we have a saved version of these external icons, we load them up immediately + int coi = getCurrentOrientationIndexForGlobalIcons(); + if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null || + sAppMarketIcon[coi] == null) { + updateAppMarketIcon(); + searchVisible = updateGlobalSearchIcon(); + voiceVisible = updateVoiceSearchIcon(searchVisible); + } + if (sGlobalSearchIcon[coi] != null) { + updateGlobalSearchIcon(sGlobalSearchIcon[coi]); + searchVisible = true; + } + if (sVoiceSearchIcon[coi] != null) { + updateVoiceSearchIcon(sVoiceSearchIcon[coi]); + voiceVisible = true; + } + if (sAppMarketIcon[coi] != null) { + updateAppMarketIcon(sAppMarketIcon[coi]); + } + mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); + } + + private void checkForLocaleChange() { + if (sLocaleConfiguration == null) { + new AsyncTask() { + @Override + protected LocaleConfiguration doInBackground(Void... unused) { + LocaleConfiguration localeConfiguration = new LocaleConfiguration(); + readConfiguration(Launcher.this, localeConfiguration); + return localeConfiguration; + } + + @Override + protected void onPostExecute(LocaleConfiguration result) { + sLocaleConfiguration = result; + checkForLocaleChange(); // recursive, but now with a locale configuration + } + }.execute(); + return; + } + + final Configuration configuration = getResources().getConfiguration(); + + final String previousLocale = sLocaleConfiguration.locale; + final String locale = configuration.locale.toString(); + + final int previousMcc = sLocaleConfiguration.mcc; + final int mcc = configuration.mcc; + + final int previousMnc = sLocaleConfiguration.mnc; + final int mnc = configuration.mnc; + + boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; + + if (localeChanged) { + sLocaleConfiguration.locale = locale; + sLocaleConfiguration.mcc = mcc; + sLocaleConfiguration.mnc = mnc; + + mIconCache.flush(); + + final LocaleConfiguration localeConfiguration = sLocaleConfiguration; + new Thread("WriteLocaleConfiguration") { + @Override + public void run() { + writeConfiguration(Launcher.this, localeConfiguration); + } + }.start(); + } + } + + private static class LocaleConfiguration { + public String locale; + public int mcc = -1; + public int mnc = -1; + } + + private static void readConfiguration(Context context, LocaleConfiguration configuration) { + DataInputStream in = null; + try { + in = new DataInputStream(context.openFileInput(PREFERENCES)); + configuration.locale = in.readUTF(); + configuration.mcc = in.readInt(); + configuration.mnc = in.readInt(); + } catch (FileNotFoundException e) { + // Ignore + } catch (IOException e) { + // Ignore + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + + private static void writeConfiguration(Context context, LocaleConfiguration configuration) { + DataOutputStream out = null; + try { + out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE)); + out.writeUTF(configuration.locale); + out.writeInt(configuration.mcc); + out.writeInt(configuration.mnc); + out.flush(); + } catch (FileNotFoundException e) { + // Ignore + } catch (IOException e) { + //noinspection ResultOfMethodCallIgnored + context.getFileStreamPath(PREFERENCES).delete(); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + + public DragLayer getDragLayer() { + return mDragLayer; + } + + boolean isDraggingEnabled() { + // We prevent dragging when we are loading the workspace as it is possible to pick up a view + // that is subsequently removed from the workspace in startBinding(). + return !mModel.isLoadingWorkspace(); + } + + static int getScreen() { + synchronized (sLock) { + return sScreen; + } + } + + static void setScreen(int screen) { + synchronized (sLock) { + sScreen = screen; + } + } + + /** + * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have + * a configuration step, this allows the proper animations to run after other transitions. + */ + private boolean completeAdd(PendingAddArguments args) { + boolean result = false; + switch (args.requestCode) { + case REQUEST_PICK_APPLICATION: + completeAddApplication(args.intent, args.container, args.screen, args.cellX, + args.cellY); + break; + case REQUEST_PICK_SHORTCUT: + processShortcut(args.intent); + break; + case REQUEST_CREATE_SHORTCUT: + completeAddShortcut(args.intent, args.container, args.screen, args.cellX, + args.cellY); + result = true; + break; + case REQUEST_CREATE_APPWIDGET: + int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + completeAddAppWidget(appWidgetId, args.container, args.screen, null, null); + result = true; + break; + case REQUEST_PICK_WALLPAPER: + // We just wanted the activity result here so we can clear mWaitingForResult + break; + } + // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen, + // if you turned the screen off and then back while in All Apps, Launcher would not + // return to the workspace. Clearing mAddInfo.container here fixes this issue + resetAddInfo(); + return result; + } + + @Override + protected void onActivityResult( + final int requestCode, final int resultCode, final Intent data) { + if (requestCode == REQUEST_BIND_APPWIDGET) { + int appWidgetId = data != null ? + data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; + if (resultCode == RESULT_CANCELED) { + completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); + } else if (resultCode == RESULT_OK) { + addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo); + } + return; + } + boolean delayExitSpringLoadedMode = false; + boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || + requestCode == REQUEST_CREATE_APPWIDGET); + mWaitingForResult = false; + + // We have special handling for widgets + if (isWidgetDrop) { + int appWidgetId = data != null ? + data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; + if (appWidgetId < 0) { + Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" + + "widget configuration activity."); + completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); + } else { + completeTwoStageWidgetDrop(resultCode, appWidgetId); + } + return; + } + + // The pattern used here is that a user PICKs a specific application, + // which, depending on the target, might need to CREATE the actual target. + + // For example, the user would PICK_SHORTCUT for "Music playlist", and we + // launch over to the Music app to actually CREATE_SHORTCUT. + if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { + final PendingAddArguments args = new PendingAddArguments(); + args.requestCode = requestCode; + args.intent = data; + args.container = mPendingAddInfo.container; + args.screen = mPendingAddInfo.screen; + args.cellX = mPendingAddInfo.cellX; + args.cellY = mPendingAddInfo.cellY; + if (isWorkspaceLocked()) { + sPendingAddList.add(args); + } else { + delayExitSpringLoadedMode = completeAdd(args); + } + } + mDragLayer.clearAnimatedView(); + // Exit spring loaded mode if necessary after cancelling the configuration of a widget + exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode, + null); + } + + private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) { + CellLayout cellLayout = + (CellLayout) mWorkspace.getChildAt(mPendingAddInfo.screen); + Runnable onCompleteRunnable = null; + int animationType = 0; + + AppWidgetHostView boundWidget = null; + if (resultCode == RESULT_OK) { + animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION; + final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, + mPendingAddWidgetInfo); + boundWidget = layout; + onCompleteRunnable = new Runnable() { + @Override + public void run() { + completeAddAppWidget(appWidgetId, mPendingAddInfo.container, + mPendingAddInfo.screen, layout, null); + exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false, + null); + } + }; + } else if (resultCode == RESULT_CANCELED) { + animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION; + onCompleteRunnable = new Runnable() { + @Override + public void run() { + exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false, + null); + } + }; + } + if (mDragLayer.getAnimatedView() != null) { + mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout, + (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable, + animationType, boundWidget, true); + } else { + // The animated view may be null in the case of a rotation during widget configuration + onCompleteRunnable.run(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + // Process any items that were added while Launcher was away + InstallShortcutReceiver.flushInstallQueue(this); + + mPaused = false; + if (mRestoring || mOnResumeNeedsLoad) { + mWorkspaceLoading = true; + mModel.startLoader(true); + mRestoring = false; + mOnResumeNeedsLoad = false; + } + + // Reset the pressed state of icons that were locked in the press state while activities + // were launching + if (mWaitingForResume != null) { + // Resets the previous workspace icon press state + mWaitingForResume.setStayPressed(false); + } + if (mAppsCustomizeContent != null) { + // Resets the previous all apps icon press state + mAppsCustomizeContent.resetDrawableState(); + } + // It is possible that widgets can receive updates while launcher is not in the foreground. + // Consequently, the widgets will be inflated in the orientation of the foreground activity + // (framework issue). On resuming, we ensure that any widgets are inflated for the current + // orientation. + getWorkspace().reinflateWidgetsIfNecessary(); + + // Again, as with the above scenario, it's possible that one or more of the global icons + // were updated in the wrong orientation. + updateGlobalIcons(); + } + + @Override + protected void onPause() { + // NOTE: We want all transitions from launcher to act as if the wallpaper were enabled + // to be consistent. So re-enable the flag here, and we will re-disable it as necessary + // when Launcher resumes and we are still in AllApps. + updateWallpaperVisibility(true); + + super.onPause(); + mPaused = true; + mDragController.cancelDrag(); + mDragController.resetLastGestureUpTime(); + } + + @Override + public Object onRetainNonConfigurationInstance() { + // Flag the loader to stop early before switching + mModel.stopLoader(); + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.surrender(); + } + return Boolean.TRUE; + } + + // We can't hide the IME if it was forced open. So don't bother + /* + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + + if (hasFocus) { + final InputMethodManager inputManager = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + WindowManager.LayoutParams lp = getWindow().getAttributes(); + inputManager.hideSoftInputFromWindow(lp.token, 0, new android.os.ResultReceiver(new + android.os.Handler()) { + protected void onReceiveResult(int resultCode, Bundle resultData) { + Log.d(TAG, "ResultReceiver got resultCode=" + resultCode); + } + }); + Log.d(TAG, "called hideSoftInputFromWindow from onWindowFocusChanged"); + } + } + */ + + private boolean acceptFilter() { + final InputMethodManager inputManager = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + return !inputManager.isFullscreenMode(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + final int uniChar = event.getUnicodeChar(); + final boolean handled = super.onKeyDown(keyCode, event); + final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar); + if (!handled && acceptFilter() && isKeyNotWhitespace) { + boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, + keyCode, event); + if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { + // something usable has been typed - start a search + // the typed text will be retrieved and cleared by + // showSearchDialog() + // If there are multiple keystrokes before the search dialog takes focus, + // onSearchRequested() will be called for every keystroke, + // but it is idempotent, so it's fine. + return onSearchRequested(); + } + } + + // Eat the long press event so the keyboard doesn't come up. + if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) { + return true; + } + + return handled; + } + + private String getTypedText() { + return mDefaultKeySsb.toString(); + } + + private void clearTypedText() { + mDefaultKeySsb.clear(); + mDefaultKeySsb.clearSpans(); + Selection.setSelection(mDefaultKeySsb, 0); + } + + /** + * 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. + */ + private void restoreState(Bundle savedState) { + if (savedState == null) { + return; + } + + State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal())); + if (state == State.APPS_CUSTOMIZE) { + showAllApps(false); + } + + int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1); + if (currentScreen > -1) { + mWorkspace.setCurrentPage(currentScreen); + } + + final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1); + final int pendingAddScreen = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); + + if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) { + mPendingAddInfo.container = pendingAddContainer; + mPendingAddInfo.screen = pendingAddScreen; + mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); + mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); + mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); + mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); + mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); + mWaitingForResult = true; + mRestoring = true; + } + + + boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); + if (renameFolder) { + long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); + mFolderInfo = mModel.getFolderById(this, sFolders, id); + mRestoring = true; + } + + + // Restore the AppsCustomize tab + if (mAppsCustomizeTabHost != null) { + String curTab = savedState.getString("apps_customize_currentTab"); + if (curTab != null) { + // We set this directly so that there is no delay before the tab is set + mAppsCustomizeContent.setContentType( + mAppsCustomizeTabHost.getContentTypeForTabTag(curTab)); + mAppsCustomizeTabHost.setCurrentTabByTag(curTab); + mAppsCustomizeContent.loadAssociatedPages( + mAppsCustomizeContent.getCurrentPage()); + } + + int currentIndex = savedState.getInt("apps_customize_currentIndex"); + mAppsCustomizeContent.restorePageForIndex(currentIndex); + } + } + + /** + * Finds all the views we need and configure them properly. + */ + private void setupViews() { + final DragController dragController = mDragController; + + mDragLayer = (DragLayer) findViewById(R.id.drag_layer); + mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); + mQsbDivider = (ImageView) findViewById(R.id.qsb_divider); + mDockDivider = (ImageView) findViewById(R.id.dock_divider); + + // Setup the drag layer + mDragLayer.setup(this, dragController); + + // Setup the hotseat + mHotseat = (Hotseat) findViewById(R.id.hotseat); + if (mHotseat != null) { + mHotseat.setup(this); + } + + // Setup the workspace + mWorkspace.setHapticFeedbackEnabled(false); + mWorkspace.setOnLongClickListener(this); + mWorkspace.setup(dragController); + dragController.addDragListener(mWorkspace); + + // Get the search/delete bar + mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar); + + // Setup AppsCustomize + mAppsCustomizeTabHost = (AppsCustomizeTabHost) + findViewById(R.id.apps_customize_pane); + mAppsCustomizeContent = (AppsCustomizePagedView) + mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); + mAppsCustomizeTabHost.setup(this); + mAppsCustomizeContent.setup(this, dragController); + + // Get the all apps button + mAllAppsButton = findViewById(R.id.all_apps_button); + if (mAllAppsButton != null) { + mAllAppsButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + onTouchDownAllAppsButton(v); + } + return false; + } + }); + } + // Setup the drag controller (drop targets have to be added in reverse order in priority) + dragController.setDragScoller(mWorkspace); + dragController.setScrollView(mDragLayer); + dragController.setMoveTarget(mWorkspace); + dragController.addDropTarget(mWorkspace); + if (mSearchDropTargetBar != null) { + mSearchDropTargetBar.setup(this, dragController); + } + } + + /** + * Creates a view representing a shortcut. + * + * @param info The data structure describing the shortcut. + * + * @return A View inflated from R.layout.application. + */ + View createShortcut(ShortcutInfo info) { + return createShortcut(R.layout.application, + (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info); + } + + /** + * Creates a view representing a shortcut inflated from the specified resource. + * + * @param layoutResId The id of the XML layout used to create the shortcut. + * @param parent The group the shortcut belongs to. + * @param info The data structure describing the shortcut. + * + * @return A View inflated from layoutResId. + */ + View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { + BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false); + favorite.applyFromShortcutInfo(info, mIconCache); + favorite.setOnClickListener(this); + return favorite; + } + + /** + * Add an application shortcut to the workspace. + * + * @param data The intent describing the application. + * @param cellInfo The position on screen where to create the shortcut. + */ + void completeAddApplication(Intent data, long container, int screen, int cellX, int cellY) { + final int[] cellXY = mTmpAddItemCellCoordinates; + final CellLayout layout = getCellLayout(container, screen); + + // First we check if we already know the exact location where we want to add this item. + if (cellX >= 0 && cellY >= 0) { + cellXY[0] = cellX; + cellXY[1] = cellY; + } else if (!layout.findCellForSpan(cellXY, 1, 1)) { + showOutOfSpaceMessage(isHotseatLayout(layout)); + return; + } + + final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this); + + if (info != null) { + info.setActivity(data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + info.container = ItemInfo.NO_ID; + mWorkspace.addApplicationShortcut(info, layout, container, screen, cellXY[0], cellXY[1], + isWorkspaceLocked(), cellX, cellY); + } else { + Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data); + } + } + + /** + * Add a shortcut to the workspace. + * + * @param data The intent describing the shortcut. + * @param cellInfo The position on screen where to create the shortcut. + */ + private void completeAddShortcut(Intent data, long container, int screen, int cellX, + int cellY) { + int[] cellXY = mTmpAddItemCellCoordinates; + int[] touchXY = mPendingAddInfo.dropPos; + CellLayout layout = getCellLayout(container, screen); + + boolean foundCellSpan = false; + + ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null); + if (info == null) { + return; + } + final View view = createShortcut(info); + + // First we check if we already know the exact location where we want to add this item. + if (cellX >= 0 && cellY >= 0) { + cellXY[0] = cellX; + cellXY[1] = cellY; + foundCellSpan = true; + + // If appropriate, either create a folder or add to an existing folder + if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, + true, null,null)) { + return; + } + DragObject dragObject = new DragObject(); + dragObject.dragInfo = info; + if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, + true)) { + return; + } + } else if (touchXY != null) { + // when dragging and dropping, just find the closest free spot + int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY); + foundCellSpan = (result != null); + } else { + foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); + } + + if (!foundCellSpan) { + showOutOfSpaceMessage(isHotseatLayout(layout)); + return; + } + + LauncherModel.addItemToDatabase(this, info, container, screen, cellXY[0], cellXY[1], false); + + if (!mRestoring) { + mWorkspace.addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, + isWorkspaceLocked()); + } + } + + static int[] getSpanForWidget(Context context, ComponentName component, int minWidth, + int minHeight) { + Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null); + // We want to account for the extra amount of padding that we are adding to the widget + // to ensure that it gets the full amount of space that it has requested + int requiredWidth = minWidth + padding.left + padding.right; + int requiredHeight = minHeight + padding.top + padding.bottom; + return CellLayout.rectToCell(context.getResources(), requiredWidth, requiredHeight, null); + } + + static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) { + return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight); + } + + static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) { + return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight); + } + + static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) { + return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight); + } + + static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) { + return getSpanForWidget(context, info.componentName, info.minResizeWidth, + info.minResizeHeight); + } + + /** + * Add a widget to the workspace. + * + * @param appWidgetId The app widget id + * @param cellInfo The position on screen where to create the widget. + */ + private void completeAddAppWidget(final int appWidgetId, long container, int screen, + AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) { + if (appWidgetInfo == null) { + appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + } + + // Calculate the grid spans needed to fit this widget + CellLayout layout = getCellLayout(container, screen); + + int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo); + int[] spanXY = getSpanForWidget(this, appWidgetInfo); + + // Try finding open space on Launcher screen + // 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 + int[] cellXY = mTmpAddItemCellCoordinates; + int[] touchXY = mPendingAddInfo.dropPos; + int[] finalSpan = new int[2]; + boolean foundCellSpan = false; + if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) { + cellXY[0] = mPendingAddInfo.cellX; + cellXY[1] = mPendingAddInfo.cellY; + spanXY[0] = mPendingAddInfo.spanX; + spanXY[1] = mPendingAddInfo.spanY; + foundCellSpan = true; + } else if (touchXY != null) { + // when dragging and dropping, just find the closest free spot + int[] result = layout.findNearestVacantArea( + touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0], + spanXY[1], cellXY, finalSpan); + spanXY[0] = finalSpan[0]; + spanXY[1] = finalSpan[1]; + foundCellSpan = (result != null); + } else { + foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]); + } + + if (!foundCellSpan) { + if (appWidgetId != -1) { + // Deleting an app widget ID is a void call but writes to disk before returning + // to the caller... + new Thread("deleteAppWidgetId") { + public void run() { + mAppWidgetHost.deleteAppWidgetId(appWidgetId); + } + }.start(); + } + showOutOfSpaceMessage(isHotseatLayout(layout)); + return; + } + + // Build Launcher-specific widget info and save to database + LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId, + appWidgetInfo.provider); + launcherInfo.spanX = spanXY[0]; + launcherInfo.spanY = spanXY[1]; + launcherInfo.minSpanX = mPendingAddInfo.minSpanX; + launcherInfo.minSpanY = mPendingAddInfo.minSpanY; + + LauncherModel.addItemToDatabase(this, launcherInfo, + container, screen, cellXY[0], cellXY[1], false); + + if (!mRestoring) { + if (hostView == null) { + // Perform actual inflation because we're live + launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); + } else { + // The AppWidgetHostView has already been inflated and instantiated + launcherInfo.hostView = hostView; + } + + launcherInfo.hostView.setTag(launcherInfo); + launcherInfo.hostView.setVisibility(View.VISIBLE); + launcherInfo.notifyWidgetSizeChanged(this); + mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1], + launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); + + addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo); + } + resetAddInfo(); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + mUserPresent = false; + mDragLayer.clearAllResizeFrames(); + updateRunning(); + + // Reset AllApps to its initial state only if we are not in the middle of + // processing a multi-step drop + if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) { + mAppsCustomizeTabHost.reset(); + showWorkspace(false); + } + } else if (Intent.ACTION_USER_PRESENT.equals(action)) { + mUserPresent = true; + updateRunning(); + } + } + }; + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + // Listen for broadcasts related to user-presence + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_USER_PRESENT); + registerReceiver(mReceiver, filter); + + mAttached = true; + mVisible = true; + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mVisible = false; + + if (mAttached) { + unregisterReceiver(mReceiver); + mAttached = false; + } + updateRunning(); + } + + public void onWindowVisibilityChanged(int visibility) { + mVisible = visibility == View.VISIBLE; + updateRunning(); + // The following code used to be in onResume, but it turns out onResume is called when + // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged + // is a more appropriate event to handle + if (mVisible) { + mAppsCustomizeTabHost.onWindowVisible(); + if (!mWorkspaceLoading) { + final ViewTreeObserver observer = mWorkspace.getViewTreeObserver(); + // We want to let Launcher draw itself at least once before we force it to build + // layers on all the workspace pages, so that transitioning to Launcher from other + // apps is nice and speedy. Usually the first call to preDraw doesn't correspond to + // a true draw so we wait until the second preDraw call to be safe + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + // We delay the layer building a bit in order to give + // other message processing a time to run. In particular + // this avoids a delay in hiding the IME if it was + // currently shown, because doing that may involve + // some communication back with the app. + mWorkspace.postDelayed(mBuildLayersRunnable, 500); + + observer.removeOnPreDrawListener(this); + return true; + } + }); + } + // When Launcher comes back to foreground, a different Activity might be responsible for + // the app market intent, so refresh the icon + updateAppMarketIcon(); + clearTypedText(); + } + } + + private void sendAdvanceMessage(long delay) { + mHandler.removeMessages(ADVANCE_MSG); + Message msg = mHandler.obtainMessage(ADVANCE_MSG); + mHandler.sendMessageDelayed(msg, delay); + mAutoAdvanceSentTime = System.currentTimeMillis(); + } + + private void updateRunning() { + boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty(); + if (autoAdvanceRunning != mAutoAdvanceRunning) { + mAutoAdvanceRunning = autoAdvanceRunning; + if (autoAdvanceRunning) { + long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft; + sendAdvanceMessage(delay); + } else { + if (!mWidgetsToAdvance.isEmpty()) { + mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - + (System.currentTimeMillis() - mAutoAdvanceSentTime)); + } + mHandler.removeMessages(ADVANCE_MSG); + mHandler.removeMessages(0); // Remove messages sent using postDelayed() + } + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == ADVANCE_MSG) { + int i = 0; + for (View key: mWidgetsToAdvance.keySet()) { + final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); + final int delay = mAdvanceStagger * i; + if (v instanceof Advanceable) { + postDelayed(new Runnable() { + public void run() { + ((Advanceable) v).advance(); + } + }, delay); + } + i++; + } + sendAdvanceMessage(mAdvanceInterval); + } + } + }; + + void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) { + if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return; + View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId); + if (v instanceof Advanceable) { + mWidgetsToAdvance.put(hostView, appWidgetInfo); + ((Advanceable) v).fyiWillBeAdvancedByHostKThx(); + updateRunning(); + } + } + + void removeWidgetToAutoAdvance(View hostView) { + if (mWidgetsToAdvance.containsKey(hostView)) { + mWidgetsToAdvance.remove(hostView); + updateRunning(); + } + } + + public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) { + removeWidgetToAutoAdvance(launcherInfo.hostView); + launcherInfo.hostView = null; + } + + void showOutOfSpaceMessage(boolean isHotseatLayout) { + int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space); + Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show(); + } + + public LauncherAppWidgetHost getAppWidgetHost() { + return mAppWidgetHost; + } + + public LauncherModel getModel() { + return mModel; + } + + void closeSystemDialogs() { + getWindow().closeAllPanels(); + + // Whatever we were doing is hereby canceled. + mWaitingForResult = false; + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + // Close the menu + if (Intent.ACTION_MAIN.equals(intent.getAction())) { + // also will cancel mWaitingForResult. + closeSystemDialogs(); + + boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) + != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + + Folder openFolder = mWorkspace.getOpenFolder(); + // In all these cases, only animate if we're already on home + mWorkspace.exitWidgetResizeMode(); + if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() && + openFolder == null) { + mWorkspace.moveToDefaultScreen(true); + } + + closeFolder(); + exitSpringLoadedDragMode(); + showWorkspace(alreadyOnHome); + + final View v = getWindow().peekDecorView(); + if (v != null && v.getWindowToken() != null) { + InputMethodManager imm = (InputMethodManager)getSystemService( + INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + + // Reset AllApps to its initial state + if (!alreadyOnHome && mAppsCustomizeTabHost != null) { + mAppsCustomizeTabHost.reset(); + } + } + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + // Do not call super here + mSavedInstanceState = savedInstanceState; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage()); + super.onSaveInstanceState(outState); + + outState.putInt(RUNTIME_STATE, mState.ordinal()); + // We close any open folder since it will not be re-opened, and we need to make sure + // this state is reflected. + closeFolder(); + + if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screen > -1 && + mWaitingForResult) { + outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container); + outState.putInt(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screen); + outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX); + outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY); + outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX); + outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY); + outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo); + } + + if (mFolderInfo != null && mWaitingForResult) { + outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); + outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); + } + + // Save the current AppsCustomize tab + if (mAppsCustomizeTabHost != null) { + String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag(); + if (currentTabTag != null) { + outState.putString("apps_customize_currentTab", currentTabTag); + } + int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex(); + outState.putInt("apps_customize_currentIndex", currentIndex); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // Remove all pending runnables + mHandler.removeMessages(ADVANCE_MSG); + mHandler.removeMessages(0); + mWorkspace.removeCallbacks(mBuildLayersRunnable); + + // Stop callbacks from LauncherModel + LauncherApplication app = ((LauncherApplication) getApplication()); + mModel.stopLoader(); + app.setLauncher(null); + + try { + mAppWidgetHost.stopListening(); + } catch (NullPointerException ex) { + Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); + } + mAppWidgetHost = null; + + mWidgetsToAdvance.clear(); + + TextKeyListener.getInstance().release(); + + + unbindWorkspaceAndHotseatItems(); + + getContentResolver().unregisterContentObserver(mWidgetObserver); + unregisterReceiver(mCloseSystemDialogsReceiver); + + mDragLayer.clearAllResizeFrames(); + ((ViewGroup) mWorkspace.getParent()).removeAllViews(); + mWorkspace.removeAllViews(); + mWorkspace = null; + mDragController = null; + + ValueAnimator.clearAllAnimations(); + } + + public DragController getDragController() { + return mDragController; + } + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + if (requestCode >= 0) mWaitingForResult = true; + super.startActivityForResult(intent, requestCode); + } + + /** + * Indicates that we want global search for this activity by setting the globalSearch + * argument for {@link #startSearch} to true. + */ + @Override + public void startSearch(String initialQuery, boolean selectInitialQuery, + Bundle appSearchData, boolean globalSearch) { + + showWorkspace(true); + + if (initialQuery == null) { + // Use any text typed in the launcher as the initial query + initialQuery = getTypedText(); + } + if (appSearchData == null) { + appSearchData = new Bundle(); + appSearchData.putString(Search.SOURCE, "launcher-search"); + } + Rect sourceBounds = mSearchDropTargetBar.getSearchBarBounds(); + + final SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), + appSearchData, globalSearch, sourceBounds); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (isWorkspaceLocked()) { + return false; + } + + super.onCreateOptionsMenu(menu); + + Intent manageApps = new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS); + manageApps.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS); + settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + String helpUrl = getString(R.string.help_url); + Intent help = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl)); + help.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + + menu.add(MENU_GROUP_WALLPAPER, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper) + .setIcon(android.R.drawable.ic_menu_gallery) + .setAlphabeticShortcut('W'); + menu.add(0, MENU_MANAGE_APPS, 0, R.string.menu_manage_apps) + .setIcon(android.R.drawable.ic_menu_manage) + .setIntent(manageApps) + .setAlphabeticShortcut('M'); + menu.add(0, MENU_SYSTEM_SETTINGS, 0, R.string.menu_settings) + .setIcon(android.R.drawable.ic_menu_preferences) + .setIntent(settings) + .setAlphabeticShortcut('P'); + if (!helpUrl.isEmpty()) { + menu.add(0, MENU_HELP, 0, R.string.menu_help) + .setIcon(android.R.drawable.ic_menu_help) + .setIntent(help) + .setAlphabeticShortcut('H'); + } + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + if (mAppsCustomizeTabHost.isTransitioning()) { + return false; + } + boolean allAppsVisible = (mAppsCustomizeTabHost.getVisibility() == View.VISIBLE); + menu.setGroupVisible(MENU_GROUP_WALLPAPER, !allAppsVisible); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_WALLPAPER_SETTINGS: + startWallpaper(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onSearchRequested() { + startSearch(null, false, null, true); + // Use a custom animation for launching search + overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast); + return true; + } + + public boolean isWorkspaceLocked() { + return mWorkspaceLoading || mWaitingForResult; + } + + private void resetAddInfo() { + mPendingAddInfo.container = ItemInfo.NO_ID; + mPendingAddInfo.screen = -1; + mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1; + mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1; + mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1; + mPendingAddInfo.dropPos = null; + } + + void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, + AppWidgetProviderInfo appWidgetInfo) { + if (appWidgetInfo.configure != null) { + mPendingAddWidgetInfo = appWidgetInfo; + + // Launch over to configure widget, if needed + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); + intent.setComponent(appWidgetInfo.configure); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); + } else { + // Otherwise just add it + completeAddAppWidget(appWidgetId, info.container, info.screen, boundWidget, + appWidgetInfo); + // Exit spring loaded mode if necessary after adding the widget + exitSpringLoadedDragModeDelayed(true, false, null); + } + } + + /** + * Process a shortcut drop. + * + * @param componentName The name of the component + * @param screen The screen where it should be added + * @param cell The cell it should be added to, optional + * @param position The location on the screen where it was dropped, optional + */ + void processShortcutFromDrop(ComponentName componentName, long container, int screen, + int[] cell, int[] loc) { + resetAddInfo(); + mPendingAddInfo.container = container; + mPendingAddInfo.screen = screen; + mPendingAddInfo.dropPos = loc; + + if (cell != null) { + mPendingAddInfo.cellX = cell[0]; + mPendingAddInfo.cellY = cell[1]; + } + + Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); + createShortcutIntent.setComponent(componentName); + processShortcut(createShortcutIntent); + } + + /** + * Process a widget drop. + * + * @param info The PendingAppWidgetInfo of the widget being added. + * @param screen The screen where it should be added + * @param cell The cell it should be added to, optional + * @param position The location on the screen where it was dropped, optional + */ + void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen, + int[] cell, int[] span, int[] loc) { + resetAddInfo(); + mPendingAddInfo.container = info.container = container; + mPendingAddInfo.screen = info.screen = screen; + mPendingAddInfo.dropPos = loc; + mPendingAddInfo.minSpanX = info.minSpanX; + mPendingAddInfo.minSpanY = info.minSpanY; + + if (cell != null) { + mPendingAddInfo.cellX = cell[0]; + mPendingAddInfo.cellY = cell[1]; + } + if (span != null) { + mPendingAddInfo.spanX = span[0]; + mPendingAddInfo.spanY = span[1]; + } + + AppWidgetHostView hostView = info.boundWidget; + int appWidgetId; + if (hostView != null) { + appWidgetId = hostView.getAppWidgetId(); + addAppWidgetImpl(appWidgetId, info, hostView, info.info); + } else { + // In this case, we either need to start an activity to get permission to bind + // the widget, or we need to start an activity to configure the widget, or both. + appWidgetId = getAppWidgetHost().allocateAppWidgetId(); + if (mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.componentName)) { + addAppWidgetImpl(appWidgetId, info, null, info.info); + } else { + mPendingAddWidgetInfo = info.info; + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName); + startActivityForResult(intent, REQUEST_BIND_APPWIDGET); + } + } + } + + void processShortcut(Intent intent) { + // Handle case where user selected "Applications" + String applicationName = getResources().getString(R.string.group_applications); + String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + + if (applicationName != null && applicationName.equals(shortcutName)) { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); + pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application)); + startActivityForResultSafely(pickIntent, REQUEST_PICK_APPLICATION); + } else { + startActivityForResultSafely(intent, REQUEST_CREATE_SHORTCUT); + } + } + + void processWallpaper(Intent intent) { + startActivityForResult(intent, REQUEST_PICK_WALLPAPER); + } + + FolderIcon addFolder(CellLayout layout, long container, final int screen, int cellX, + int cellY) { + final FolderInfo folderInfo = new FolderInfo(); + folderInfo.title = getText(R.string.folder_name); + + // Update the model + LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screen, cellX, cellY, + false); + sFolders.put(folderInfo.id, folderInfo); + + // Create the view + FolderIcon newFolder = + FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache); + mWorkspace.addInScreen(newFolder, container, screen, cellX, cellY, 1, 1, + isWorkspaceLocked()); + return newFolder; + } + + void removeFolder(FolderInfo folder) { + sFolders.remove(folder.id); + } + + private void startWallpaper() { + showWorkspace(true); + final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); + Intent chooser = Intent.createChooser(pickWallpaper, + getText(R.string.chooser_wallpaper)); + // NOTE: Adds a configure option to the chooser if the wallpaper supports it + // Removed in Eclair MR1 +// WallpaperManager wm = (WallpaperManager) +// getSystemService(Context.WALLPAPER_SERVICE); +// WallpaperInfo wi = wm.getWallpaperInfo(); +// if (wi != null && wi.getSettingsActivity() != null) { +// LabeledIntent li = new LabeledIntent(getPackageName(), +// R.string.configure_wallpaper, 0); +// li.setClassName(wi.getPackageName(), wi.getSettingsActivity()); +// chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { li }); +// } + startActivityForResult(chooser, REQUEST_PICK_WALLPAPER); + } + + /** + * Registers various content observers. The current implementation registers + * only a favorites observer to keep track of the favorites applications. + */ + private void registerContentObservers() { + ContentResolver resolver = getContentResolver(); + resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, + true, mWidgetObserver); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_HOME: + return true; + case KeyEvent.KEYCODE_VOLUME_DOWN: + if (SystemProperties.getInt("debug.launcher2.dumpstate", 0) != 0) { + dumpState(); + return true; + } + break; + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_HOME: + return true; + } + } + + return super.dispatchKeyEvent(event); + } + + @Override + public void onBackPressed() { + if (mState == State.APPS_CUSTOMIZE) { + showWorkspace(true); + } else if (mWorkspace.getOpenFolder() != null) { + Folder openFolder = mWorkspace.getOpenFolder(); + if (openFolder.isEditingName()) { + openFolder.dismissEditingName(); + } else { + closeFolder(); + } + } else { + mWorkspace.exitWidgetResizeMode(); + + // Back button is a no-op here, but give at least some feedback for the button press + mWorkspace.showOutlinesTemporarily(); + } + } + + /** + * Re-listen when widgets are reset. + */ + private void onAppWidgetReset() { + if (mAppWidgetHost != null) { + mAppWidgetHost.startListening(); + } + } + + /** + * Go through the and disconnect any of the callbacks in the drawables and the views or we + * leak the previous Home screen on orientation change. + */ + private void unbindWorkspaceAndHotseatItems() { + if (mModel != null) { + mModel.unbindWorkspaceItems(); + } + } + + /** + * Launches the intent referred by the clicked shortcut. + * + * @param v The view representing the clicked shortcut. + */ + public void onClick(View v) { + // Make sure that rogue clicks don't get through while allapps is launching, or after the + // view has detached (it's possible for this to happen if the view is removed mid touch). + if (v.getWindowToken() == null) { + return; + } + + if (!mWorkspace.isFinishedSwitchingState()) { + return; + } + + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + // Open shortcut + final Intent intent = ((ShortcutInfo) tag).intent; + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + intent.setSourceBounds(new Rect(pos[0], pos[1], + pos[0] + v.getWidth(), pos[1] + v.getHeight())); + + boolean success = startActivitySafely(v, intent, tag); + + if (success && v instanceof BubbleTextView) { + mWaitingForResume = (BubbleTextView) v; + mWaitingForResume.setStayPressed(true); + } + } else if (tag instanceof FolderInfo) { + if (v instanceof FolderIcon) { + FolderIcon fi = (FolderIcon) v; + handleFolderClick(fi); + } + } else if (v == mAllAppsButton) { + if (mState == State.APPS_CUSTOMIZE) { + showWorkspace(true); + } else { + onClickAllAppsButton(v); + } + } + } + + 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) { + v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + + onSearchRequested(); + } + + /** + * Event handler for the voice button + * + * @param v The view that was clicked. + */ + public void onClickVoiceButton(View v) { + v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + + try { + final SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + ComponentName activityName = searchManager.getGlobalSearchActivity(); + Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (activityName != null) { + intent.setPackage(activityName.getPackageName()); + } + startActivity(null, intent, "onClickVoiceButton"); + overridePendingTransition(R.anim.fade_in_fast, R.anim.fade_out_fast); + } catch (ActivityNotFoundException e) { + Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivitySafely(null, intent, "onClickVoiceButton"); + } + } + + /** + * 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 onTouchDownAllAppsButton(View v) { + // Provide the same haptic feedback that the system offers for virtual keys. + v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + } + + public void onClickAppMarketButton(View v) { + if (mAppMarketIntent != null) { + startActivitySafely(v, mAppMarketIntent, "app market"); + } else { + Log.e(TAG, "Invalid app market intent."); + } + } + + void startApplicationDetailsActivity(ComponentName componentName) { + String packageName = componentName.getPackageName(); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", packageName, null)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + startActivitySafely(null, intent, "startApplicationDetailsActivity"); + } + + 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)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + startActivity(intent); + } + } + + boolean startActivity(View v, Intent intent, Object tag) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + try { + // Only launch using the new animation if the shortcut has not opted out (this is a + // private contract between launcher and may be ignored in the future). + boolean useLaunchAnimation = (v != null) && + !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); + if (useLaunchAnimation) { + ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0, + v.getMeasuredWidth(), v.getMeasuredHeight()); + + startActivity(intent, opts.toBundle()); + } else { + startActivity(intent); + } + return true; + } catch (SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Launcher does not have the permission to launch " + intent + + ". Make sure to create a MAIN intent-filter for the corresponding activity " + + "or use the exported attribute for this activity. " + + "tag="+ tag + " intent=" + intent, e); + } + return false; + } + + boolean startActivitySafely(View v, Intent intent, Object tag) { + boolean success = false; + try { + success = startActivity(v, intent, tag); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); + } + return success; + } + + void startActivityForResultSafely(Intent intent, int requestCode) { + try { + startActivityForResult(intent, requestCode); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + } catch (SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Launcher does not have the permission to launch " + intent + + ". Make sure to create a MAIN intent-filter for the corresponding activity " + + "or use the exported attribute for this activity.", e); + } + } + + private void handleFolderClick(FolderIcon folderIcon) { + final FolderInfo info = folderIcon.mInfo; + Folder openFolder = mWorkspace.getFolderForTag(info); + + // If the folder info reports that the associated folder is open, then verify that + // it is actually opened. There have been a few instances where this gets out of sync. + if (info.opened && openFolder == null) { + Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: " + + info.screen + " (" + info.cellX + ", " + info.cellY + ")"); + info.opened = false; + } + + if (!info.opened) { + // Close any open folder + closeFolder(); + // Open the requested folder + openFolder(folderIcon); + } else { + // Find the open folder... + int folderScreen; + if (openFolder != null) { + folderScreen = mWorkspace.getPageForView(openFolder); + // .. and close it + closeFolder(openFolder); + if (folderScreen != mWorkspace.getCurrentPage()) { + // Close any folder open on the current screen + closeFolder(); + // Pull the folder onto this screen + openFolder(folderIcon); + } + } + } + } + + /** + * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView + * in the DragLayer in the exact absolute location of the original FolderIcon. + */ + private void copyFolderIconToImage(FolderIcon fi) { + final int width = fi.getMeasuredWidth(); + final int height = fi.getMeasuredHeight(); + + // Lazy load ImageView, Bitmap and Canvas + if (mFolderIconImageView == null) { + mFolderIconImageView = new ImageView(this); + } + if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width || + mFolderIconBitmap.getHeight() != height) { + mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mFolderIconCanvas = new Canvas(mFolderIconBitmap); + } + + DragLayer.LayoutParams lp; + if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) { + lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams(); + } else { + lp = new DragLayer.LayoutParams(width, height); + } + + mDragLayer.getViewRectRelativeToSelf(fi, mRectForFolderAnimation); + lp.customPosition = true; + lp.x = mRectForFolderAnimation.left; + lp.y = mRectForFolderAnimation.top; + lp.width = width; + lp.height = height; + + mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + fi.draw(mFolderIconCanvas); + mFolderIconImageView.setImageBitmap(mFolderIconBitmap); + if (fi.mFolder != null) { + mFolderIconImageView.setPivotX(fi.mFolder.getPivotXForIconAnimation()); + mFolderIconImageView.setPivotY(fi.mFolder.getPivotYForIconAnimation()); + } + // Just in case this image view is still in the drag layer from a previous animation, + // we remove it and re-add it. + if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) { + mDragLayer.removeView(mFolderIconImageView); + } + mDragLayer.addView(mFolderIconImageView, lp); + if (fi.mFolder != null) { + fi.mFolder.bringToFront(); + } + } + + private void growAndFadeOutFolderIcon(FolderIcon fi) { + if (fi == null) return; + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); + PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f); + + FolderInfo info = (FolderInfo) fi.getTag(); + if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + CellLayout cl = (CellLayout) fi.getParent().getParent(); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams(); + cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); + } + + // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original + copyFolderIconToImage(fi); + fi.setVisibility(View.INVISIBLE); + + ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(mFolderIconImageView, alpha, + scaleX, scaleY); + oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration)); + oa.start(); + } + + private void shrinkAndFadeInFolderIcon(final FolderIcon fi) { + if (fi == null) return; + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); + PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); + + final CellLayout cl = (CellLayout) fi.getParent().getParent(); + + // We remove and re-draw the FolderIcon in-case it has changed + mDragLayer.removeView(mFolderIconImageView); + copyFolderIconToImage(fi); + ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(mFolderIconImageView, alpha, + scaleX, scaleY); + oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration)); + oa.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (cl != null) { + cl.clearFolderLeaveBehind(); + // Remove the ImageView copy of the FolderIcon and make the original visible. + mDragLayer.removeView(mFolderIconImageView); + fi.setVisibility(View.VISIBLE); + } + } + }); + oa.start(); + } + + /** + * 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. + */ + public void openFolder(FolderIcon folderIcon) { + Folder folder = folderIcon.mFolder; + FolderInfo info = folder.mInfo; + + info.opened = true; + + // Just verify that the folder hasn't already been added to the DragLayer. + // There was a one-off crash where the folder had a parent already. + if (folder.getParent() == null) { + mDragLayer.addView(folder); + mDragController.addDropTarget((DropTarget) folder); + } else { + Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" + + folder.getParent() + ")."); + } + folder.animateOpen(); + growAndFadeOutFolderIcon(folderIcon); + } + + public void closeFolder() { + Folder folder = mWorkspace.getOpenFolder(); + if (folder != null) { + if (folder.isEditingName()) { + folder.dismissEditingName(); + } + closeFolder(folder); + + // Dismiss the folder cling + dismissFolderCling(null); + } + } + + void closeFolder(Folder folder) { + folder.getInfo().opened = false; + + ViewGroup parent = (ViewGroup) folder.getParent().getParent(); + if (parent != null) { + FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo); + shrinkAndFadeInFolderIcon(fi); + } + folder.animateClosed(); + } + + public boolean onLongClick(View v) { + if (!isDraggingEnabled()) return false; + if (isWorkspaceLocked()) return false; + if (mState != State.WORKSPACE) return false; + + if (!(v instanceof CellLayout)) { + v = (View) v.getParent().getParent(); + } + + resetAddInfo(); + CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); + // This happens when long clicking an item with the dpad/trackball + if (longClickCellInfo == null) { + return true; + } + + // The hotseat touch handling does not go through Workspace, and we always allow long press + // on hotseat items. + final View itemUnderLongClick = longClickCellInfo.cell; + boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress(); + if (allowLongPress && !mDragController.isDragging()) { + if (itemUnderLongClick == null) { + // User long pressed on empty space + mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + startWallpaper(); + } else { + if (!(itemUnderLongClick instanceof Folder)) { + // User long pressed on an item + mWorkspace.startDrag(longClickCellInfo); + } + } + } + return true; + } + + boolean isHotseatLayout(View layout) { + return mHotseat != null && layout != null && + (layout instanceof CellLayout) && (layout == mHotseat.getLayout()); + } + Hotseat getHotseat() { + return mHotseat; + } + SearchDropTargetBar getSearchBar() { + return mSearchDropTargetBar; + } + + /** + * Returns the CellLayout of the specified container at the specified screen. + */ + CellLayout getCellLayout(long container, int screen) { + if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (mHotseat != null) { + return mHotseat.getLayout(); + } else { + return null; + } + } else { + return (CellLayout) mWorkspace.getChildAt(screen); + } + } + + Workspace getWorkspace() { + return mWorkspace; + } + + // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. + public boolean isAllAppsVisible() { + return (mState == State.APPS_CUSTOMIZE); + } + + public boolean isAllAppsButtonRank(int rank) { + return mHotseat.isAllAppsButtonRank(rank); + } + + // AllAppsView.Watcher + public void zoomed(float zoom) { + if (zoom == 1.0f) { + mWorkspace.setVisibility(View.GONE); + } + } + + /** + * 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 (eg. APPS_CUSTOMIZE) + * @param scaleFactor The scale factor used for the zoom + */ + private void setPivotsForZoom(View view, float scaleFactor) { + view.setPivotX(view.getWidth() / 2.0f); + view.setPivotY(view.getHeight() / 2.0f); + } + + void disableWallpaperIfInAllApps() { + // Only disable it if we are in all apps + if (mState == State.APPS_CUSTOMIZE) { + if (mAppsCustomizeTabHost != null && + !mAppsCustomizeTabHost.isTransitioning()) { + updateWallpaperVisibility(false); + } + } + } + + void updateWallpaperVisibility(boolean visible) { + int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; + int curflags = getWindow().getAttributes().flags + & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + if (wpflags != curflags) { + getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); + } + } + + private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { + if (v instanceof LauncherTransitionable) { + ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace); + } + } + + private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { + if (v instanceof LauncherTransitionable) { + ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace); + } + + // Update the workspace transition step as well + dispatchOnLauncherTransitionStep(v, 0f); + } + + private void dispatchOnLauncherTransitionStep(View v, float t) { + if (v instanceof LauncherTransitionable) { + ((LauncherTransitionable) v).onLauncherTransitionStep(this, t); + } + } + + private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { + if (v instanceof LauncherTransitionable) { + ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace); + } + + // Update the workspace transition step as well + dispatchOnLauncherTransitionStep(v, 1f); + } + + /** + * Things to test when changing the following seven functions. + * - Home from workspace + * - from center screen + * - from other screens + * - Home from all apps + * - from center screen + * - from other screens + * - Back from all apps + * - from center screen + * - from other screens + * - Launch app from workspace and quit + * - with back + * - with home + * - Launch app from all apps and quit + * - with back + * - with home + * - Go to a screen that's not the default, then all + * apps, and launch and app, and go back + * - with back + * -with home + * - On workspace, long press power and go back + * - with back + * - with home + * - On all apps, long press power and go back + * - with back + * - with home + * - On workspace, power off + * - On all apps, power off + * - Launch an app and turn off the screen while in that app + * - Go back with home key + * - Go back with back key TODO: make this not go to workspace + * - From all apps + * - From workspace + * - Enter and exit car mode (becuase it causes an extra configuration changed) + * - From all apps + * - From the center workspace + * - From another workspace + */ + + /** + * 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. + */ + private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) { + if (mStateAnimation != null) { + mStateAnimation.cancel(); + mStateAnimation = null; + } + final Resources res = getResources(); + + final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime); + final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime); + final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); + final View fromView = mWorkspace; + final AppsCustomizeTabHost toView = mAppsCustomizeTabHost; + final int startDelay = + res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger); + + setPivotsForZoom(toView, scale); + + // Shrink workspaces away if going to AppsCustomize from workspace + Animator workspaceAnim = + mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated); + + if (animated) { + toView.setScaleX(scale); + toView.setScaleY(scale); + final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView); + scaleAnim. + scaleX(1f).scaleY(1f). + setDuration(duration). + setInterpolator(new Workspace.ZoomOutInterpolator()); + + toView.setVisibility(View.VISIBLE); + toView.setAlpha(0f); + final ObjectAnimator alphaAnim = ObjectAnimator + .ofFloat(toView, "alpha", 0f, 1f) + .setDuration(fadeDuration); + alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f)); + alphaAnim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (Float) animation.getAnimatedValue(); + dispatchOnLauncherTransitionStep(fromView, t); + dispatchOnLauncherTransitionStep(toView, t); + } + }); + + // toView should appear right at the end of the workspace shrink + // animation + mStateAnimation = new AnimatorSet(); + mStateAnimation.play(scaleAnim).after(startDelay); + mStateAnimation.play(alphaAnim).after(startDelay); + + mStateAnimation.addListener(new AnimatorListenerAdapter() { + boolean animationCancelled = false; + + @Override + public void onAnimationStart(Animator animation) { + updateWallpaperVisibility(true); + // Prepare the position + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setVisibility(View.VISIBLE); + toView.bringToFront(); + } + @Override + public void onAnimationEnd(Animator animation) { + dispatchOnLauncherTransitionEnd(fromView, animated, false); + dispatchOnLauncherTransitionEnd(toView, animated, false); + + if (!springLoaded && !LauncherApplication.isScreenLarge()) { + // Hide the workspace scrollbar + mWorkspace.hideScrollingIndicator(true); + hideDockDivider(); + } + if (!animationCancelled) { + updateWallpaperVisibility(false); + } + + // Hide the search bar + mSearchDropTargetBar.hideSearchBar(false); + } + + @Override + public void onAnimationCancel(Animator animation) { + animationCancelled = true; + } + }); + + if (workspaceAnim != null) { + mStateAnimation.play(workspaceAnim); + } + + boolean delayAnim = false; + final ViewTreeObserver observer; + + dispatchOnLauncherTransitionPrepare(fromView, animated, false); + dispatchOnLauncherTransitionPrepare(toView, animated, false); + + // If any of the objects being animated haven't been measured/laid out + // yet, delay the animation until we get a layout pass + if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) || + (mWorkspace.getMeasuredWidth() == 0) || + (toView.getMeasuredWidth() == 0)) { + observer = mWorkspace.getViewTreeObserver(); + delayAnim = true; + } else { + observer = null; + } + + final AnimatorSet stateAnimation = mStateAnimation; + final Runnable startAnimRunnable = new Runnable() { + public void run() { + // Check that mStateAnimation hasn't changed while + // we waited for a layout/draw pass + if (mStateAnimation != stateAnimation) + return; + setPivotsForZoom(toView, scale); + dispatchOnLauncherTransitionStart(fromView, animated, false); + dispatchOnLauncherTransitionStart(toView, animated, false); + toView.post(new Runnable() { + public void run() { + // Check that mStateAnimation hasn't changed while + // we waited for a layout/draw pass + if (mStateAnimation != stateAnimation) + return; + mStateAnimation.start(); + } + }); + } + }; + if (delayAnim) { + final OnGlobalLayoutListener delayedStart = new OnGlobalLayoutListener() { + public void onGlobalLayout() { + toView.post(startAnimRunnable); + observer.removeOnGlobalLayoutListener(this); + } + }; + observer.addOnGlobalLayoutListener(delayedStart); + } else { + startAnimRunnable.run(); + } + } else { + toView.setTranslationX(0.0f); + toView.setTranslationY(0.0f); + toView.setScaleX(1.0f); + toView.setScaleY(1.0f); + toView.setVisibility(View.VISIBLE); + toView.bringToFront(); + + if (!springLoaded && !LauncherApplication.isScreenLarge()) { + // Hide the workspace scrollbar + mWorkspace.hideScrollingIndicator(true); + hideDockDivider(); + + // Hide the search bar + mSearchDropTargetBar.hideSearchBar(false); + } + dispatchOnLauncherTransitionPrepare(fromView, animated, false); + dispatchOnLauncherTransitionStart(fromView, animated, false); + dispatchOnLauncherTransitionEnd(fromView, animated, false); + dispatchOnLauncherTransitionPrepare(toView, animated, false); + dispatchOnLauncherTransitionStart(toView, animated, false); + dispatchOnLauncherTransitionEnd(toView, animated, false); + updateWallpaperVisibility(false); + } + } + + /** + * Zoom the camera back into the workspace, hiding 'fromView'. + * This is the opposite of showAppsCustomizeHelper. + * @param animated If true, the transition will be animated. + */ + private void hideAppsCustomizeHelper(State toState, final boolean animated, + final boolean springLoaded, final Runnable onCompleteRunnable) { + + if (mStateAnimation != null) { + mStateAnimation.cancel(); + mStateAnimation = null; + } + Resources res = getResources(); + + final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime); + final int fadeOutDuration = + res.getInteger(R.integer.config_appsCustomizeFadeOutTime); + final float scaleFactor = (float) + res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); + final View fromView = mAppsCustomizeTabHost; + final View toView = mWorkspace; + Animator workspaceAnim = null; + + if (toState == State.WORKSPACE) { + int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger); + workspaceAnim = mWorkspace.getChangeStateAnimation( + Workspace.State.NORMAL, animated, stagger); + } else if (toState == State.APPS_CUSTOMIZE_SPRING_LOADED) { + workspaceAnim = mWorkspace.getChangeStateAnimation( + Workspace.State.SPRING_LOADED, animated); + } + + setPivotsForZoom(fromView, scaleFactor); + updateWallpaperVisibility(true); + showHotseat(animated); + if (animated) { + final LauncherViewPropertyAnimator scaleAnim = + new LauncherViewPropertyAnimator(fromView); + scaleAnim. + scaleX(scaleFactor).scaleY(scaleFactor). + setDuration(duration). + setInterpolator(new Workspace.ZoomInInterpolator()); + + final ObjectAnimator alphaAnim = ObjectAnimator + .ofFloat(fromView, "alpha", 1f, 0f) + .setDuration(fadeOutDuration); + alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator()); + alphaAnim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = 1f - (Float) animation.getAnimatedValue(); + dispatchOnLauncherTransitionStep(fromView, t); + dispatchOnLauncherTransitionStep(toView, t); + } + }); + + mStateAnimation = new AnimatorSet(); + + dispatchOnLauncherTransitionPrepare(fromView, animated, true); + dispatchOnLauncherTransitionPrepare(toView, animated, true); + + mStateAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + updateWallpaperVisibility(true); + fromView.setVisibility(View.GONE); + dispatchOnLauncherTransitionEnd(fromView, animated, true); + dispatchOnLauncherTransitionEnd(toView, animated, true); + if (mWorkspace != null) { + mWorkspace.hideScrollingIndicator(false); + } + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + } + }); + + mStateAnimation.playTogether(scaleAnim, alphaAnim); + if (workspaceAnim != null) { + mStateAnimation.play(workspaceAnim); + } + dispatchOnLauncherTransitionStart(fromView, animated, true); + dispatchOnLauncherTransitionStart(toView, animated, true); + final Animator stateAnimation = mStateAnimation; + mWorkspace.post(new Runnable() { + public void run() { + if (stateAnimation != mStateAnimation) + return; + mStateAnimation.start(); + } + }); + } else { + fromView.setVisibility(View.GONE); + dispatchOnLauncherTransitionPrepare(fromView, animated, true); + dispatchOnLauncherTransitionStart(fromView, animated, true); + dispatchOnLauncherTransitionEnd(fromView, animated, true); + dispatchOnLauncherTransitionPrepare(toView, animated, true); + dispatchOnLauncherTransitionStart(toView, animated, true); + dispatchOnLauncherTransitionEnd(toView, animated, true); + mWorkspace.hideScrollingIndicator(false); + } + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { + mAppsCustomizeTabHost.onTrimMemory(); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if (!hasFocus) { + // When another window occludes launcher (like the notification shade, or recents), + // ensure that we enable the wallpaper flag so that transitions are done correctly. + updateWallpaperVisibility(true); + } else { + // When launcher has focus again, disable the wallpaper if we are in AllApps + mWorkspace.postDelayed(new Runnable() { + @Override + public void run() { + disableWallpaperIfInAllApps(); + } + }, 500); + } + } + + void showWorkspace(boolean animated) { + showWorkspace(animated, null); + } + + void showWorkspace(boolean animated, Runnable onCompleteRunnable) { + if (mState != State.WORKSPACE) { + boolean wasInSpringLoadedMode = (mState == State.APPS_CUSTOMIZE_SPRING_LOADED); + mWorkspace.setVisibility(View.VISIBLE); + hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable); + + // Show the search bar (only animate if we were showing the drop target bar in spring + // loaded mode) + mSearchDropTargetBar.showSearchBar(wasInSpringLoadedMode); + + // We only need to animate in the dock divider if we're going from spring loaded mode + showDockDivider(animated && wasInSpringLoadedMode); + + // Set focus to the AppsCustomize button + if (mAllAppsButton != null) { + mAllAppsButton.requestFocus(); + } + } + + mWorkspace.flashScrollingIndicator(animated); + + // Change the state *after* we've called all the transition code + mState = State.WORKSPACE; + + // Resume the auto-advance of widgets + mUserPresent = true; + updateRunning(); + + // send an accessibility event to announce the context change + getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + + void showAllApps(boolean animated) { + if (mState != State.WORKSPACE) return; + + showAppsCustomizeHelper(animated, false); + mAppsCustomizeTabHost.requestFocus(); + + // Change the state *after* we've called all the transition code + mState = State.APPS_CUSTOMIZE; + + // Pause the auto-advance of widgets until we are out of AllApps + mUserPresent = false; + updateRunning(); + closeFolder(); + + // Send an accessibility event to announce the context change + getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + } + + void enterSpringLoadedDragMode() { + if (mState == State.APPS_CUSTOMIZE) { + hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null); + hideDockDivider(); + mState = State.APPS_CUSTOMIZE_SPRING_LOADED; + } + } + + void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay, + final Runnable onCompleteRunnable) { + if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return; + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (successfulDrop) { + // Before we show workspace, hide all apps again because + // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should + // clean up our state transition functions + mAppsCustomizeTabHost.setVisibility(View.GONE); + showWorkspace(true, onCompleteRunnable); + } else { + exitSpringLoadedDragMode(); + } + } + }, (extendedDelay ? + EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT : + EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT)); + } + + void exitSpringLoadedDragMode() { + if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) { + final boolean animated = true; + final boolean springLoaded = true; + showAppsCustomizeHelper(animated, springLoaded); + mState = State.APPS_CUSTOMIZE; + } + // Otherwise, we are not in spring loaded mode, so don't do anything. + } + + void hideDockDivider() { + if (mQsbDivider != null && mDockDivider != null) { + mQsbDivider.setVisibility(View.INVISIBLE); + mDockDivider.setVisibility(View.INVISIBLE); + } + } + + void showDockDivider(boolean animated) { + if (mQsbDivider != null && mDockDivider != null) { + mQsbDivider.setVisibility(View.VISIBLE); + mDockDivider.setVisibility(View.VISIBLE); + if (mDividerAnimator != null) { + mDividerAnimator.cancel(); + mQsbDivider.setAlpha(1f); + mDockDivider.setAlpha(1f); + mDividerAnimator = null; + } + if (animated) { + mDividerAnimator = new AnimatorSet(); + mDividerAnimator.playTogether(ObjectAnimator.ofFloat(mQsbDivider, "alpha", 1f), + ObjectAnimator.ofFloat(mDockDivider, "alpha", 1f)); + mDividerAnimator.setDuration(mSearchDropTargetBar.getTransitionInDuration()); + mDividerAnimator.start(); + } + } + } + + void lockAllApps() { + // TODO + } + + void unlockAllApps() { + // TODO + } + + public boolean isAllAppsCustomizeOpen() { + return mState == State.APPS_CUSTOMIZE; + } + + /** + * Shows the hotseat area. + */ + void showHotseat(boolean animated) { + if (!LauncherApplication.isScreenLarge()) { + if (animated) { + if (mHotseat.getAlpha() != 1f) { + int duration = mSearchDropTargetBar.getTransitionInDuration(); + mHotseat.animate().alpha(1f).setDuration(duration); + } + } else { + mHotseat.setAlpha(1f); + } + } + } + + /** + * Hides the hotseat area. + */ + void hideHotseat(boolean animated) { + if (!LauncherApplication.isScreenLarge()) { + if (animated) { + if (mHotseat.getAlpha() != 0f) { + int duration = mSearchDropTargetBar.getTransitionOutDuration(); + mHotseat.animate().alpha(0f).setDuration(duration); + } + } else { + mHotseat.setAlpha(0f); + } + } + } + + /** + * Add an item from all apps or customize onto the given workspace screen. + * If layout is null, add to the current screen. + */ + void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) { + if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) { + showOutOfSpaceMessage(isHotseatLayout(layout)); + } + } + + /** Maps the current orientation to an index for referencing orientation correct global icons */ + private int getCurrentOrientationIndexForGlobalIcons() { + // default - 0, landscape - 1 + switch (getResources().getConfiguration().orientation) { + case Configuration.ORIENTATION_LANDSCAPE: + return 1; + default: + return 0; + } + } + + private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) { + 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(resourceName); + if (iconResId != 0) { + Resources res = packageManager.getResourcesForActivity(activityName); + return res.getDrawable(iconResId); + } + } + } catch (NameNotFoundException e) { + // This can happen if the activity defines an invalid drawable + Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() + + " not found", e); + } catch (Resources.NotFoundException nfe) { + // This can happen if the activity defines an invalid drawable + Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(), + nfe); + } + return null; + } + + // if successful in getting icon, return it; otherwise, set button to use default drawable + private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity( + int buttonId, ComponentName activityName, int fallbackDrawableId, + String toolbarResourceName) { + Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName); + Resources r = getResources(); + int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width); + int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height); + + TextView button = (TextView) findViewById(buttonId); + // If we were unable to find the icon via the meta-data, use a generic one + if (toolbarIcon == null) { + toolbarIcon = r.getDrawable(fallbackDrawableId); + toolbarIcon.setBounds(0, 0, w, h); + if (button != null) { + button.setCompoundDrawables(toolbarIcon, null, null, null); + } + return null; + } else { + toolbarIcon.setBounds(0, 0, w, h); + if (button != null) { + button.setCompoundDrawables(toolbarIcon, null, null, null); + } + return toolbarIcon.getConstantState(); + } + } + + // if successful in getting icon, return it; otherwise, set button to use default drawable + private Drawable.ConstantState updateButtonWithIconFromExternalActivity( + int buttonId, ComponentName activityName, int fallbackDrawableId, + String toolbarResourceName) { + ImageView button = (ImageView) findViewById(buttonId); + Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName); + + if (button != null) { + // 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); + } + } + + return toolbarIcon != null ? toolbarIcon.getConstantState() : null; + + } + + private void updateTextButtonWithDrawable(int buttonId, Drawable d) { + TextView button = (TextView) findViewById(buttonId); + button.setCompoundDrawables(d, null, null, null); + } + + private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) { + ImageView button = (ImageView) findViewById(buttonId); + button.setImageDrawable(d.newDrawable(getResources())); + } + + private void invalidatePressedFocusedStates(View container, View button) { + if (container instanceof HolographicLinearLayout) { + HolographicLinearLayout layout = (HolographicLinearLayout) container; + layout.invalidatePressedFocusedStates(); + } else if (button instanceof HolographicImageView) { + HolographicImageView view = (HolographicImageView) button; + view.invalidatePressedFocusedStates(); + } + } + + private boolean updateGlobalSearchIcon() { + final View searchButtonContainer = findViewById(R.id.search_button_container); + final ImageView searchButton = (ImageView) findViewById(R.id.search_button); + final View searchDivider = findViewById(R.id.search_divider); + final View voiceButtonContainer = findViewById(R.id.voice_button_container); + final View voiceButton = findViewById(R.id.voice_button); + final View voiceButtonProxy = findViewById(R.id.voice_button_proxy); + + final SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + ComponentName activityName = searchManager.getGlobalSearchActivity(); + if (activityName != null) { + int coi = getCurrentOrientationIndexForGlobalIcons(); + sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity( + R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo, + TOOLBAR_SEARCH_ICON_METADATA_NAME); + if (sGlobalSearchIcon[coi] == null) { + sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity( + R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo, + TOOLBAR_ICON_METADATA_NAME); + } + + if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE); + if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE); + searchButton.setVisibility(View.VISIBLE); + invalidatePressedFocusedStates(searchButtonContainer, searchButton); + return true; + } else { + // We disable both search and voice search when there is no global search provider + if (searchDivider != null) searchDivider.setVisibility(View.GONE); + if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE); + if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE); + searchButton.setVisibility(View.GONE); + voiceButton.setVisibility(View.GONE); + if (voiceButtonProxy != null) { + voiceButtonProxy.setVisibility(View.GONE); + } + return false; + } + } + + private void updateGlobalSearchIcon(Drawable.ConstantState d) { + final View searchButtonContainer = findViewById(R.id.search_button_container); + final View searchButton = (ImageView) findViewById(R.id.search_button); + updateButtonWithDrawable(R.id.search_button, d); + invalidatePressedFocusedStates(searchButtonContainer, searchButton); + } + + private boolean updateVoiceSearchIcon(boolean searchVisible) { + final View searchDivider = findViewById(R.id.search_divider); + final View voiceButtonContainer = findViewById(R.id.voice_button_container); + final View voiceButton = findViewById(R.id.voice_button); + final View voiceButtonProxy = findViewById(R.id.voice_button_proxy); + + // We only show/update the voice search icon if the search icon is enabled as well + final SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity(); + + ComponentName activityName = null; + if (globalSearchActivity != null) { + // Check if the global search activity handles voice search + Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + intent.setPackage(globalSearchActivity.getPackageName()); + activityName = intent.resolveActivity(getPackageManager()); + } + + if (activityName == null) { + // Fallback: check if an activity other than the global search activity + // resolves this + Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + activityName = intent.resolveActivity(getPackageManager()); + } + if (searchVisible && activityName != null) { + int coi = getCurrentOrientationIndexForGlobalIcons(); + sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity( + R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo, + TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME); + if (sVoiceSearchIcon[coi] == null) { + sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity( + R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo, + TOOLBAR_ICON_METADATA_NAME); + } + if (searchDivider != null) searchDivider.setVisibility(View.VISIBLE); + if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE); + voiceButton.setVisibility(View.VISIBLE); + if (voiceButtonProxy != null) { + voiceButtonProxy.setVisibility(View.VISIBLE); + } + invalidatePressedFocusedStates(voiceButtonContainer, voiceButton); + return true; + } else { + if (searchDivider != null) searchDivider.setVisibility(View.GONE); + if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE); + voiceButton.setVisibility(View.GONE); + if (voiceButtonProxy != null) { + voiceButtonProxy.setVisibility(View.GONE); + } + return false; + } + } + + private void updateVoiceSearchIcon(Drawable.ConstantState d) { + final View voiceButtonContainer = findViewById(R.id.voice_button_container); + final View voiceButton = findViewById(R.id.voice_button); + updateButtonWithDrawable(R.id.voice_button, d); + invalidatePressedFocusedStates(voiceButtonContainer, voiceButton); + } + + /** + * Sets the app market icon + */ + private void updateAppMarketIcon() { + final View marketButton = findViewById(R.id.market_button); + 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) { + int coi = getCurrentOrientationIndexForGlobalIcons(); + mAppMarketIntent = intent; + sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity( + R.id.market_button, activityName, R.drawable.ic_launcher_market_holo, + TOOLBAR_ICON_METADATA_NAME); + marketButton.setVisibility(View.VISIBLE); + } else { + // We should hide and disable the view so that we don't try and restore the visibility + // of it when we swap between drag & normal states from IconDropTarget subclasses. + marketButton.setVisibility(View.GONE); + marketButton.setEnabled(false); + } + } + + private void updateAppMarketIcon(Drawable.ConstantState d) { + // Ensure that the new drawable we are creating has the approprate toolbar icon bounds + Resources r = getResources(); + Drawable marketIconDrawable = d.newDrawable(r); + int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width); + int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height); + marketIconDrawable.setBounds(0, 0, w, h); + + updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + boolean result = super.dispatchPopulateAccessibilityEvent(event); + final List text = event.getText(); + text.clear(); + text.add(getString(R.string.home)); + return result; + } + + /** + * Receives notifications when system dialogs are to be closed. + */ + private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + closeSystemDialogs(); + } + } + + /** + * Receives notifications whenever the appwidgets are reset. + */ + private class AppWidgetResetObserver extends ContentObserver { + public AppWidgetResetObserver() { + super(new Handler()); + } + + @Override + public void onChange(boolean selfChange) { + onAppWidgetReset(); + } + } + + /** + * If the activity is currently paused, signal that we need to re-run the loader + * in onResume. + * + * This needs to be called from incoming places where resources might have been loaded + * while we are paused. That is becaues the Configuration might be wrong + * when we're not running, and if it comes back to what it was when we + * were paused, we are not restarted. + * + * Implementation of the method from LauncherModel.Callbacks. + * + * @return true if we are currently paused. The caller might be able to + * skip some work in that case since we will come back again. + */ + public boolean setLoadOnResume() { + if (mPaused) { + Log.i(TAG, "setLoadOnResume"); + mOnResumeNeedsLoad = true; + return true; + } else { + return false; + } + } + + /** + * Implementation of the method from LauncherModel.Callbacks. + */ + public int getCurrentWorkspaceScreen() { + if (mWorkspace != null) { + return mWorkspace.getCurrentPage(); + } else { + return SCREEN_COUNT / 2; + } + } + + /** + * Refreshes the shortcuts shown on the workspace. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void startBinding() { + final Workspace workspace = mWorkspace; + + mNewShortcutAnimatePage = -1; + mNewShortcutAnimateViews.clear(); + mWorkspace.clearDropTargets(); + int count = workspace.getChildCount(); + for (int i = 0; i < count; i++) { + // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate(). + final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i); + layoutParent.removeAllViewsInLayout(); + } + mWidgetsToAdvance.clear(); + if (mHotseat != null) { + mHotseat.resetLayout(); + } + } + + /** + * Bind the items start-end from the list. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindItems(ArrayList shortcuts, int start, int end) { + setLoadOnResume(); + + // Get the list of added shortcuts and intersect them with the set of shortcuts here + Set newApps = new HashSet(); + newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps); + + Workspace workspace = mWorkspace; + for (int i = start; i < end; i++) { + final ItemInfo item = shortcuts.get(i); + + // Short circuit if we are loading dock items for a configuration which has no dock + if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && + mHotseat == null) { + continue; + } + + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + ShortcutInfo info = (ShortcutInfo) item; + String uri = info.intent.toUri(0).toString(); + View shortcut = createShortcut(info); + workspace.addInScreen(shortcut, item.container, item.screen, item.cellX, + item.cellY, 1, 1, false); + boolean animateIconUp = false; + synchronized (newApps) { + if (newApps.contains(uri)) { + animateIconUp = newApps.remove(uri); + } + } + if (animateIconUp) { + // Prepare the view to be animated up + shortcut.setAlpha(0f); + shortcut.setScaleX(0f); + shortcut.setScaleY(0f); + mNewShortcutAnimatePage = item.screen; + if (!mNewShortcutAnimateViews.contains(shortcut)) { + mNewShortcutAnimateViews.add(shortcut); + } + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, + (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), + (FolderInfo) item, mIconCache); + workspace.addInScreen(newFolder, item.container, item.screen, item.cellX, + item.cellY, 1, 1, false); + break; + } + } + + workspace.requestLayout(); + } + + /** + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindFolders(HashMap folders) { + setLoadOnResume(); + sFolders.clear(); + sFolders.putAll(folders); + } + + /** + * Add the views for a widget to the workspace. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAppWidget(LauncherAppWidgetInfo item) { + setLoadOnResume(); + + final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; + if (DEBUG_WIDGETS) { + Log.d(TAG, "bindAppWidget: " + item); + } + final Workspace workspace = mWorkspace; + + final int appWidgetId = item.appWidgetId; + final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + if (DEBUG_WIDGETS) { + Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); + } + + item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + + item.hostView.setTag(item); + item.onBindAppWidget(this); + + workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX, + item.cellY, item.spanX, item.spanY, false); + addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo); + + workspace.requestLayout(); + + if (DEBUG_WIDGETS) { + Log.d(TAG, "bound widget id="+item.appWidgetId+" in " + + (SystemClock.uptimeMillis()-start) + "ms"); + } + } + + /** + * Callback saying that there aren't any more items to bind. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void finishBindingItems() { + setLoadOnResume(); + + if (mSavedState != null) { + if (!mWorkspace.hasFocus()) { + mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); + } + mSavedState = null; + } + + if (mSavedInstanceState != null) { + super.onRestoreInstanceState(mSavedInstanceState); + mSavedInstanceState = null; + } + + // If we received the result of any pending adds while the loader was running (e.g. the + // widget configuration forced an orientation change), process them now. + for (int i = 0; i < sPendingAddList.size(); i++) { + completeAdd(sPendingAddList.get(i)); + } + sPendingAddList.clear(); + + // Update the market app icon as necessary (the other icons will be managed in response to + // package changes in bindSearchablesChanged() + updateAppMarketIcon(); + + // Animate up any icons as necessary + if (mVisible || mWorkspaceLoading) { + Runnable newAppsRunnable = new Runnable() { + @Override + public void run() { + runNewAppsAnimation(false); + } + }; + + boolean willSnapPage = mNewShortcutAnimatePage > -1 && + mNewShortcutAnimatePage != mWorkspace.getCurrentPage(); + if (canRunNewAppsAnimation()) { + // If the user has not interacted recently, then either snap to the new page to show + // the new-apps animation or just run them if they are to appear on the current page + if (willSnapPage) { + mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable); + } else { + runNewAppsAnimation(false); + } + } else { + // If the user has interacted recently, then just add the items in place if they + // are on another page (or just normally if they are added to the current page) + runNewAppsAnimation(willSnapPage); + } + } + + mWorkspaceLoading = false; + } + + private boolean canRunNewAppsAnimation() { + long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime(); + return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000); + } + + /** + * Runs a new animation that scales up icons that were added while Launcher was in the + * background. + * + * @param immediate whether to run the animation or show the results immediately + */ + private void runNewAppsAnimation(boolean immediate) { + AnimatorSet anim = new AnimatorSet(); + Collection bounceAnims = new ArrayList(); + + // Order these new views spatially so that they animate in order + Collections.sort(mNewShortcutAnimateViews, new Comparator() { + @Override + public int compare(View a, View b) { + CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams(); + CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams(); + int cellCountX = LauncherModel.getCellCountX(); + return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX); + } + }); + + // Animate each of the views in place (or show them immediately if requested) + if (immediate) { + for (View v : mNewShortcutAnimateViews) { + v.setAlpha(1f); + v.setScaleX(1f); + v.setScaleY(1f); + } + } else { + for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) { + View v = mNewShortcutAnimateViews.get(i); + ValueAnimator bounceAnim = ObjectAnimator.ofPropertyValuesHolder(v, + PropertyValuesHolder.ofFloat("alpha", 1f), + PropertyValuesHolder.ofFloat("scaleX", 1f), + PropertyValuesHolder.ofFloat("scaleY", 1f)); + bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); + bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); + bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator()); + bounceAnims.add(bounceAnim); + } + anim.playTogether(bounceAnims); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mWorkspace.postDelayed(mBuildLayersRunnable, 500); + } + }); + anim.start(); + } + + // Clean up + mNewShortcutAnimatePage = -1; + mNewShortcutAnimateViews.clear(); + new Thread("clearNewAppsThread") { + public void run() { + mSharedPrefs.edit() + .putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1) + .putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null) + .commit(); + } + }.start(); + } + + @Override + public void bindSearchablesChanged() { + boolean searchVisible = updateGlobalSearchIcon(); + boolean voiceVisible = updateVoiceSearchIcon(searchVisible); + mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); + } + + /** + * Add the icons for all apps. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAllApplications(final ArrayList apps) { + // Remove the progress bar entirely; we could also make it GONE + // but better to remove it since we know it's not going to be used + View progressBar = mAppsCustomizeTabHost. + findViewById(R.id.apps_customize_progress_bar); + if (progressBar != null) { + ((ViewGroup)progressBar.getParent()).removeView(progressBar); + } + // We just post the call to setApps so the user sees the progress bar + // disappear-- otherwise, it just looks like the progress bar froze + // which doesn't look great + mAppsCustomizeTabHost.post(new Runnable() { + public void run() { + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.setApps(apps); + } + } + }); + } + + /** + * A package was installed. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAppsAdded(ArrayList apps) { + setLoadOnResume(); + + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.addApps(apps); + } + } + + /** + * A package was updated. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAppsUpdated(ArrayList apps) { + setLoadOnResume(); + if (mWorkspace != null) { + mWorkspace.updateShortcuts(apps); + } + + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.updateApps(apps); + } + } + + /** + * A package was uninstalled. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAppsRemoved(ArrayList apps, boolean permanent) { + if (permanent) { + mWorkspace.removeItems(apps); + } + + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.removeApps(apps); + } + + // Notify the drag controller + mDragController.onAppsRemoved(apps, this); + } + + /** + * A number of packages were updated. + */ + public void bindPackagesUpdated() { + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.onPackagesUpdated(); + } + } + + private int mapConfigurationOriActivityInfoOri(int configOri) { + final Display d = getWindowManager().getDefaultDisplay(); + int naturalOri = Configuration.ORIENTATION_LANDSCAPE; + switch (d.getRotation()) { + case Surface.ROTATION_0: + case Surface.ROTATION_180: + // We are currently in the same basic orientation as the natural orientation + naturalOri = configOri; + break; + case Surface.ROTATION_90: + case Surface.ROTATION_270: + // We are currently in the other basic orientation to the natural orientation + naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ? + Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + break; + } + + int[] oriMap = { + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, + ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, + ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + }; + // Since the map starts at portrait, we need to offset if this device's natural orientation + // is landscape. + int indexOffset = 0; + if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) { + indexOffset = 1; + } + return oriMap[(d.getRotation() + indexOffset) % 4]; + } + + public boolean isRotationEnabled() { + boolean forceEnableRotation = "true".equalsIgnoreCase(SystemProperties.get( + FORCE_ENABLE_ROTATION_PROPERTY, "false")); + boolean enableRotation = forceEnableRotation || + getResources().getBoolean(R.bool.allow_rotation); + return enableRotation; + } + public void lockScreenOrientation() { + if (isRotationEnabled()) { + setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources() + .getConfiguration().orientation)); + } + } + public void unlockScreenOrientation(boolean immediate) { + if (isRotationEnabled()) { + if (immediate) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } else { + mHandler.postDelayed(new Runnable() { + public void run() { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } + }, mRestoreScreenOrientationDelay); + } + } + } + + /* Cling related */ + private boolean isClingsEnabled() { + // disable clings when running in a test harness + if(ActivityManager.isRunningInTestHarness()) return false; + + return true; + } + private Cling initCling(int clingId, int[] positionData, boolean animate, int delay) { + Cling cling = (Cling) findViewById(clingId); + if (cling != null) { + cling.init(this, positionData); + cling.setVisibility(View.VISIBLE); + cling.setLayerType(View.LAYER_TYPE_HARDWARE, null); + cling.requestAccessibilityFocus(); + if (animate) { + cling.buildLayer(); + cling.setAlpha(0f); + cling.animate() + .alpha(1f) + .setInterpolator(new AccelerateInterpolator()) + .setDuration(SHOW_CLING_DURATION) + .setStartDelay(delay) + .start(); + } else { + cling.setAlpha(1f); + } + } + return cling; + } + private void dismissCling(final Cling cling, final String flag, int duration) { + if (cling != null) { + ObjectAnimator anim = ObjectAnimator.ofFloat(cling, "alpha", 0f); + anim.setDuration(duration); + anim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + cling.setVisibility(View.GONE); + cling.cleanup(); + // We should update the shared preferences on a background thread + new Thread("dismissClingThread") { + public void run() { + SharedPreferences.Editor editor = mSharedPrefs.edit(); + editor.putBoolean(flag, true); + editor.commit(); + } + }.start(); + }; + }); + anim.start(); + } + } + private void removeCling(int id) { + final View cling = findViewById(id); + if (cling != null) { + final ViewGroup parent = (ViewGroup) cling.getParent(); + parent.post(new Runnable() { + @Override + public void run() { + parent.removeView(cling); + } + }); + } + } + + private boolean skipCustomClingIfNoAccounts() { + Cling cling = (Cling) findViewById(R.id.workspace_cling); + boolean customCling = cling.getDrawIdentifier().equals("workspace_custom"); + if (customCling) { + AccountManager am = AccountManager.get(this); + Account[] accounts = am.getAccountsByType("com.google"); + return accounts.length == 0; + } + return false; + } + + public void showFirstRunWorkspaceCling() { + // Enable the clings only if they have not been dismissed before + if (isClingsEnabled() && + !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false) && + !skipCustomClingIfNoAccounts() ) { + initCling(R.id.workspace_cling, null, false, 0); + } else { + removeCling(R.id.workspace_cling); + } + } + public void showFirstRunAllAppsCling(int[] position) { + // Enable the clings only if they have not been dismissed before + if (isClingsEnabled() && + !mSharedPrefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) { + initCling(R.id.all_apps_cling, position, true, 0); + } else { + removeCling(R.id.all_apps_cling); + } + } + public Cling showFirstRunFoldersCling() { + // Enable the clings only if they have not been dismissed before + if (isClingsEnabled() && + !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) { + return initCling(R.id.folder_cling, null, true, 0); + } else { + removeCling(R.id.folder_cling); + return null; + } + } + public boolean isFolderClingVisible() { + Cling cling = (Cling) findViewById(R.id.folder_cling); + if (cling != null) { + return cling.getVisibility() == View.VISIBLE; + } + return false; + } + public void dismissWorkspaceCling(View v) { + Cling cling = (Cling) findViewById(R.id.workspace_cling); + dismissCling(cling, Cling.WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); + } + public void dismissAllAppsCling(View v) { + Cling cling = (Cling) findViewById(R.id.all_apps_cling); + dismissCling(cling, Cling.ALLAPPS_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); + } + public void dismissFolderCling(View v) { + Cling cling = (Cling) findViewById(R.id.folder_cling); + dismissCling(cling, Cling.FOLDER_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); + } + + /** + * Prints out out state for debugging. + */ + public void dumpState() { + Log.d(TAG, "BEGIN launcher2 dump state for launcher " + this); + Log.d(TAG, "mSavedState=" + mSavedState); + Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); + Log.d(TAG, "mRestoring=" + mRestoring); + Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); + Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState); + Log.d(TAG, "sFolders.size=" + sFolders.size()); + mModel.dumpState(); + + if (mAppsCustomizeContent != null) { + mAppsCustomizeContent.dumpState(); + } + Log.d(TAG, "END launcher2 dump state"); + } + + @Override + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + super.dump(prefix, fd, writer, args); + writer.println(" "); + writer.println("Debug logs: "); + for (int i = 0; i < sDumpLogs.size(); i++) { + writer.println(" " + sDumpLogs.get(i)); + } + } +} + +interface LauncherTransitionable { + View getContent(); + void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace); + void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace); + void onLauncherTransitionStep(Launcher l, float t); + void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace); +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherAnimatorUpdateListener.java b/src/com/cyanogenmod/trebuchet/LauncherAnimatorUpdateListener.java new file mode 100644 index 000000000..df9ded1ea --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherAnimatorUpdateListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; + +abstract class LauncherAnimatorUpdateListener implements AnimatorUpdateListener { + public void onAnimationUpdate(ValueAnimator animation) { + final float b = (Float) animation.getAnimatedValue(); + final float a = 1f - b; + onAnimationUpdate(a, b); + } + + abstract void onAnimationUpdate(float a, float b); +} \ No newline at end of file diff --git a/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHost.java b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHost.java new file mode 100644 index 000000000..3f12a036c --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHost.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; + +/** + * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} + * which correctly captures all long-press events. This ensures that users can + * always pick up and move widgets. + */ +public class LauncherAppWidgetHost extends AppWidgetHost { + public LauncherAppWidgetHost(Context context, int hostId) { + super(context, hostId); + } + + @Override + protected AppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + return new LauncherAppWidgetHostView(context); + } + + @Override + public void stopListening() { + super.stopListening(); + clearViews(); + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHostView.java b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHostView.java new file mode 100644 index 000000000..5eea2806a --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHostView.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.appwidget.AppWidgetHostView; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Parcel; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RemoteViews; + +import com.cyanogenmod.trebuchet.R; + +/** + * {@inheritDoc} + */ +public class LauncherAppWidgetHostView extends AppWidgetHostView { + private CheckLongPressHelper mLongPressHelper; + private LayoutInflater mInflater; + private Context mContext; + private int mPreviousOrientation; + + public LauncherAppWidgetHostView(Context context) { + super(context); + mContext = context; + mLongPressHelper = new CheckLongPressHelper(this); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + protected View getErrorView() { + return mInflater.inflate(R.layout.appwidget_error, this, false); + } + + @Override + public void updateAppWidget(RemoteViews remoteViews) { + // Store the orientation in which the widget was inflated + mPreviousOrientation = mContext.getResources().getConfiguration().orientation; + super.updateAppWidget(remoteViews); + } + + public boolean orientationChangedSincedInflation() { + int orientation = mContext.getResources().getConfiguration().orientation; + if (mPreviousOrientation != orientation) { + return true; + } + return false; + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + // Consume any touch events for ourselves after longpress is triggered + if (mLongPressHelper.hasPerformedLongPress()) { + mLongPressHelper.cancelLongPress(); + return true; + } + + // Watch for longpress events at this level to make sure + // users can always pick up this widget + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + mLongPressHelper.postCheckForLongPress(); + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mLongPressHelper.cancelLongPress(); + break; + } + + // Otherwise continue letting touch events fall through to children + return false; + } + + @Override + public void cancelLongPress() { + super.cancelLongPress(); + + mLongPressHelper.cancelLongPress(); + } + + @Override + public int getDescendantFocusability() { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java new file mode 100644 index 000000000..0ab5dae84 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.appwidget.AppWidgetHostView; +import android.content.ComponentName; +import android.content.ContentValues; + +/** + * 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 = NO_ID; + + ComponentName providerName; + + // TODO: Are these necessary here? + int minWidth = -1; + int minHeight = -1; + + private boolean mHasNotifiedInitialWidgetSizeChanged; + + /** + * View that holds this widget after it's been created. This view isn't created + * until Launcher knows it's needed. + */ + AppWidgetHostView hostView = null; + + LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { + itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; + this.appWidgetId = appWidgetId; + 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; + } + + @Override + void onAddToDatabase(ContentValues values) { + super.onAddToDatabase(values); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); + } + + /** + * When we bind the widget, we should notify the widget that the size has changed if we have not + * done so already (only really for default workspace widgets). + */ + void onBindAppWidget(Launcher launcher) { + if (!mHasNotifiedInitialWidgetSizeChanged) { + notifyWidgetSizeChanged(launcher); + } + } + + /** + * Trigger an update callback to the widget to notify it that its size has changed. + */ + void notifyWidgetSizeChanged(Launcher launcher) { + AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY); + mHasNotifiedInitialWidgetSizeChanged = true; + } + + @Override + public String toString() { + return "AppWidget(id=" + Integer.toString(appWidgetId) + ")"; + } + + @Override + void unbind() { + super.unbind(); + hostView = null; + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherApplication.java b/src/com/cyanogenmod/trebuchet/LauncherApplication.java new file mode 100644 index 000000000..7fa88177b --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherApplication.java @@ -0,0 +1,145 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.app.Application; +import android.app.SearchManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.database.ContentObserver; +import android.os.Handler; + +import com.cyanogenmod.trebuchet.R; + +import java.lang.ref.WeakReference; + +public class LauncherApplication extends Application { + public LauncherModel mModel; + public IconCache mIconCache; + private static boolean sIsScreenLarge; + private static float sScreenDensity; + private static int sLongPressTimeout = 300; + private static final String sSharedPreferencesKey = "com.cyanogenmod.trebuchet.prefs"; + WeakReference mLauncherProvider; + + @Override + public void onCreate() { + super.onCreate(); + + // set sIsScreenXLarge and sScreenDensity *before* creating icon cache + sIsScreenLarge = getResources().getBoolean(R.bool.is_large_screen); + sScreenDensity = getResources().getDisplayMetrics().density; + + mIconCache = new IconCache(this); + mModel = new LauncherModel(this, mIconCache); + + // Register intent receivers + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + registerReceiver(mModel, filter); + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + filter.addAction(Intent.ACTION_LOCALE_CHANGED); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + registerReceiver(mModel, filter); + filter = new IntentFilter(); + filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); + registerReceiver(mModel, filter); + filter = new IntentFilter(); + filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); + registerReceiver(mModel, filter); + + // Register for changes to the favorites + ContentResolver resolver = getContentResolver(); + resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, + mFavoritesObserver); + } + + /** + * There's no guarantee that this function is ever called. + */ + @Override + public void onTerminate() { + super.onTerminate(); + + unregisterReceiver(mModel); + + ContentResolver resolver = getContentResolver(); + resolver.unregisterContentObserver(mFavoritesObserver); + } + + /** + * Receives notifications whenever the user favorites have changed. + */ + private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + // If the database has ever changed, then we really need to force a reload of the + // workspace on the next load + mModel.resetLoadedState(false, true); + mModel.startLoaderFromBackground(); + } + }; + + LauncherModel setLauncher(Launcher launcher) { + mModel.initialize(launcher); + return mModel; + } + + IconCache getIconCache() { + return mIconCache; + } + + LauncherModel getModel() { + return mModel; + } + + void setLauncherProvider(LauncherProvider provider) { + mLauncherProvider = new WeakReference(provider); + } + + LauncherProvider getLauncherProvider() { + return mLauncherProvider.get(); + } + + public static String getSharedPreferencesKey() { + return sSharedPreferencesKey; + } + + public static boolean isScreenLarge() { + return sIsScreenLarge; + } + + public static boolean isScreenLandscape(Context context) { + return context.getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; + } + + public static float getScreenDensity() { + return sScreenDensity; + } + + public static int getLongPressTimeout() { + return sLongPressTimeout; + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherModel.java b/src/com/cyanogenmod/trebuchet/LauncherModel.java new file mode 100644 index 000000000..7e0df1435 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherModel.java @@ -0,0 +1,2176 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.app.SearchManager; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.BroadcastReceiver; +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.pm.ActivityInfo; +import android.content.pm.PackageInfo; +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.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.InstallWidgetReceiver.WidgetMimeTypeHandlerData; + +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; + +/** + * Maintains in-memory state of the Launcher. It is expected that there should be only one + * LauncherModel object held in a static. Also provide APIs for updating the database state + * for the Launcher. + */ +public class LauncherModel extends BroadcastReceiver { + static final boolean DEBUG_LOADERS = false; + static final String TAG = "Launcher.Model"; + + private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons + private final boolean mAppsCanBeOnExternalStorage; + private int mBatchSize; // 0 is all apps at once + private int mAllAppsLoadDelay; // milliseconds between batches + + private final LauncherApplication mApp; + private final Object mLock = new Object(); + private DeferredHandler mHandler = new DeferredHandler(); + private LoaderTask mLoaderTask; + + private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); + static { + sWorkerThread.start(); + } + private static final Handler sWorker = new Handler(sWorkerThread.getLooper()); + + // We start off with everything not loaded. After that, we assume that + // our monitoring of the package manager provides all updates and we never + // need to do a requery. These are only ever touched from the loader thread. + private boolean mWorkspaceLoaded; + private boolean mAllAppsLoaded; + + private WeakReference mCallbacks; + + // < only access in worker thread > + private AllAppsList mAllAppsList; + + // sItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by + // LauncherModel to their ids + static final HashMap sItemsIdMap = new HashMap(); + + // sItems is passed to bindItems, which expects a list of all folders and shortcuts created by + // LauncherModel that are directly on the home screen (however, no widgets or shortcuts + // within folders). + static final ArrayList sWorkspaceItems = new ArrayList(); + + // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() + static final ArrayList sAppWidgets = + new ArrayList(); + + // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() + static final HashMap sFolders = new HashMap(); + + // sDbIconCache is the set of ItemInfos that need to have their icons updated in the database + static final HashMap sDbIconCache = new HashMap(); + + // + + private IconCache mIconCache; + private Bitmap mDefaultIcon; + + private static int mCellCountX; + private static int mCellCountY; + + protected int mPreviousConfigMcc; + + public interface Callbacks { + public boolean setLoadOnResume(); + public int getCurrentWorkspaceScreen(); + public void startBinding(); + public void bindItems(ArrayList shortcuts, int start, int end); + public void bindFolders(HashMap folders); + public void finishBindingItems(); + public void bindAppWidget(LauncherAppWidgetInfo info); + public void bindAllApplications(ArrayList apps); + public void bindAppsAdded(ArrayList apps); + public void bindAppsUpdated(ArrayList apps); + public void bindAppsRemoved(ArrayList apps, boolean permanent); + public void bindPackagesUpdated(); + public boolean isAllAppsVisible(); + public boolean isAllAppsButtonRank(int rank); + public void bindSearchablesChanged(); + } + + LauncherModel(LauncherApplication app, IconCache iconCache) { + mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated(); + mApp = app; + mAllAppsList = new AllAppsList(iconCache); + mIconCache = iconCache; + + mDefaultIcon = Utilities.createIconBitmap( + mIconCache.getFullResDefaultActivityIcon(), app); + + final Resources res = app.getResources(); + mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay); + mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize); + Configuration config = res.getConfiguration(); + mPreviousConfigMcc = config.mcc; + } + + public Bitmap getFallbackIcon() { + return Bitmap.createBitmap(mDefaultIcon); + } + + public void unbindWorkspaceItems() { + sWorker.post(new Runnable() { + @Override + public void run() { + unbindWorkspaceItemsOnMainThread(); + } + }); + } + + /** Unbinds all the sWorkspaceItems on the main thread, and return a copy of sWorkspaceItems + * that is save to reference from the main thread. */ + private ArrayList unbindWorkspaceItemsOnMainThread() { + // Ensure that we don't use the same workspace items data structure on the main thread + // by making a copy of workspace items first. + final ArrayList workspaceItems = new ArrayList(sWorkspaceItems); + final ArrayList appWidgets = new ArrayList(sAppWidgets); + mHandler.post(new Runnable() { + @Override + public void run() { + for (ItemInfo item : workspaceItems) { + item.unbind(); + } + for (ItemInfo item : appWidgets) { + item.unbind(); + } + } + }); + + return workspaceItems; + } + + /** + * Adds an item to the DB if it was not created previously, or move it to a new + * + */ + static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, + int screen, int cellX, int cellY) { + if (item.container == ItemInfo.NO_ID) { + // From all apps + addItemToDatabase(context, item, container, screen, cellX, cellY, false); + } else { + // From somewhere else + moveItemInDatabase(context, item, container, screen, cellX, cellY); + } + } + + static void updateItemInDatabaseHelper(Context context, final ContentValues values, + final ItemInfo item, final String callingFunction) { + final long itemId = item.id; + final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false); + final ContentResolver cr = context.getContentResolver(); + + Runnable r = new Runnable() { + public void run() { + cr.update(uri, values, null, null); + + ItemInfo modelItem = sItemsIdMap.get(itemId); + if (item != modelItem) { + // the modelItem needs to match up perfectly with item if our model is to be + // consistent with the database-- for now, just require modelItem == item + String msg = "item: " + ((item != null) ? item.toString() : "null") + + "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") + + "Error: ItemInfo passed to " + callingFunction + " doesn't match original"; + throw new RuntimeException(msg); + } + + // Items are added/removed from the corresponding FolderInfo elsewhere, such + // as in Workspace.onDrop. Here, we just add/remove them from the list of items + // that are on the desktop, as appropriate + if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || + modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (!sWorkspaceItems.contains(modelItem)) { + sWorkspaceItems.add(modelItem); + } + } else { + sWorkspaceItems.remove(modelItem); + } + } + }; + + if (sWorkerThread.getThreadId() == Process.myTid()) { + r.run(); + } else { + sWorker.post(r); + } + } + + /** + * Move an item in the DB to a new + */ + static void moveItemInDatabase(Context context, final ItemInfo item, final long container, + final int screen, final int cellX, final int cellY) { + item.container = container; + item.cellX = cellX; + item.cellY = cellY; + + // We store hotseat items in canonical form which is this orientation invariant position + // in the hotseat + if (context instanceof Launcher && screen < 0 && + container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); + } else { + item.screen = screen; + } + + final ContentValues values = new ContentValues(); + 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.SCREEN, item.screen); + + updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); + } + + /** + * Move and/or resize item in the DB to a new + */ + static void modifyItemInDatabase(Context context, final ItemInfo item, final long container, + final int screen, final int cellX, final int cellY, final int spanX, final int spanY) { + item.container = container; + item.cellX = cellX; + item.cellY = cellY; + item.spanX = spanX; + item.spanY = spanY; + + // We store hotseat items in canonical form which is this orientation invariant position + // in the hotseat + if (context instanceof Launcher && screen < 0 && + container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); + } else { + item.screen = screen; + } + + final ContentValues values = new ContentValues(); + 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.SPANX, item.spanX); + values.put(LauncherSettings.Favorites.SPANY, item.spanY); + values.put(LauncherSettings.Favorites.SCREEN, item.screen); + + updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); + } + + /** + * Update an item to the database in a specified container. + */ + static void updateItemInDatabase(Context context, final ItemInfo item) { + final ContentValues values = new ContentValues(); + item.onAddToDatabase(values); + item.updateValuesWithCoordinates(values, item.cellX, item.cellY); + updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); + } + + /** + * Returns true if the shortcuts already exists in the database. + * we identify a shortcut by its title and intent. + */ + static boolean shortcutExists(Context context, String title, Intent intent) { + final ContentResolver cr = context.getContentResolver(); + Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, + new String[] { "title", "intent" }, "title=? and intent=?", + new String[] { title, intent.toUri(0) }, null); + boolean result = false; + try { + result = c.moveToFirst(); + } finally { + c.close(); + } + return result; + } + + /** + * Returns an ItemInfo array containing all the items in the LauncherModel. + * The ItemInfo.id is not set through this function. + */ + static ArrayList getItemsInLocalCoordinates(Context context) { + ArrayList items = new ArrayList(); + 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 folderList, long id) { + final ContentResolver cr = context.getContentResolver(); + Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, + "_id=? and (itemType=? or itemType=?)", + new String[] { String.valueOf(id), + String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null); + + try { + if (c.moveToFirst()) { + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + 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); + + FolderInfo folderInfo = null; + switch (c.getInt(itemTypeIndex)) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + folderInfo = findOrMakeFolder(folderList, id); + break; + } + + folderInfo.title = c.getString(titleIndex); + folderInfo.id = id; + folderInfo.container = c.getInt(containerIndex); + folderInfo.screen = c.getInt(screenIndex); + folderInfo.cellX = c.getInt(cellXIndex); + folderInfo.cellY = c.getInt(cellYIndex); + + return folderInfo; + } + } finally { + c.close(); + } + + return null; + } + + /** + * Add an item to the database in a specified container. Sets the container, screen, cellX and + * cellY fields of the item. Also assigns an ID to the item. + */ + static void addItemToDatabase(Context context, final ItemInfo item, final long container, + final int screen, final int cellX, final int cellY, final boolean notify) { + item.container = container; + item.cellX = cellX; + item.cellY = cellY; + // We store hotseat items in canonical form which is this orientation invariant position + // in the hotseat + if (context instanceof Launcher && screen < 0 && + container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); + } else { + item.screen = screen; + } + + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); + item.onAddToDatabase(values); + + LauncherApplication app = (LauncherApplication) context.getApplicationContext(); + item.id = app.getLauncherProvider().generateNewId(); + values.put(LauncherSettings.Favorites._ID, item.id); + item.updateValuesWithCoordinates(values, item.cellX, item.cellY); + + Runnable r = new Runnable() { + public void run() { + cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : + LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); + + if (sItemsIdMap.containsKey(item.id)) { + // we should not be adding new items in the db with the same id + throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " + + "addItemToDatabase already exists." + item.toString()); + } + sItemsIdMap.put(item.id, item); + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + sFolders.put(item.id, (FolderInfo) item); + // Fall through + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || + item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + sWorkspaceItems.add(item); + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + sAppWidgets.add((LauncherAppWidgetInfo) item); + break; + } + } + }; + + if (sWorkerThread.getThreadId() == Process.myTid()) { + r.run(); + } else { + sWorker.post(r); + } + } + + /** + * Creates a new unique child id, for a given cell span across all layouts. + */ + static int getCellLayoutChildId( + long container, int screen, int localCellX, int localCellY, int spanX, int spanY) { + return (((int) container & 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; + } + + /** + * Removes the specified item from the database + * @param context + * @param item + */ + static void deleteItemFromDatabase(Context context, final ItemInfo item) { + final ContentResolver cr = context.getContentResolver(); + final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false); + Runnable r = new Runnable() { + public void run() { + cr.delete(uriToDelete, null, null); + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + sFolders.remove(item.id); + sWorkspaceItems.remove(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + sWorkspaceItems.remove(item); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + sAppWidgets.remove((LauncherAppWidgetInfo) item); + break; + } + sItemsIdMap.remove(item.id); + sDbIconCache.remove(item); + } + }; + if (sWorkerThread.getThreadId() == Process.myTid()) { + r.run(); + } else { + sWorker.post(r); + } + } + + /** + * Remove the contents of the specified folder from the database + */ + static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) { + final ContentResolver cr = context.getContentResolver(); + + Runnable r = new Runnable() { + public void run() { + cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); + sItemsIdMap.remove(info.id); + sFolders.remove(info.id); + sDbIconCache.remove(info); + sWorkspaceItems.remove(info); + + cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, + LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); + for (ItemInfo childInfo : info.contents) { + sItemsIdMap.remove(childInfo.id); + sDbIconCache.remove(childInfo); + } + } + }; + if (sWorkerThread.getThreadId() == Process.myTid()) { + r.run(); + } else { + sWorker.post(r); + } + } + + /** + * Set this as the current Launcher activity object for the loader. + */ + public void initialize(Callbacks callbacks) { + synchronized (mLock) { + mCallbacks = new WeakReference(callbacks); + } + } + + /** + * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and + * ACTION_PACKAGE_CHANGED. + */ + @Override + 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) + || Intent.ACTION_PACKAGE_REMOVED.equals(action) + || Intent.ACTION_PACKAGE_ADDED.equals(action)) { + final String packageName = intent.getData().getSchemeSpecificPart(); + final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + + int op = PackageUpdatedTask.OP_NONE; + + if (packageName == null || packageName.length() == 0) { + // they sent us a bad intent + return; + } + + if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + op = PackageUpdatedTask.OP_UPDATE; + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + if (!replacing) { + op = PackageUpdatedTask.OP_REMOVE; + } + // else, we are replacing the package, so a PACKAGE_ADDED will be sent + // later, we will update the package at this time + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + if (!replacing) { + op = PackageUpdatedTask.OP_ADD; + } else { + op = PackageUpdatedTask.OP_UPDATE; + } + } + + if (op != PackageUpdatedTask.OP_NONE) { + enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName })); + } + + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { + // First, schedule to add these apps back in. + String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); + // Then, rebind everything. + 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/workspace. + forceReload(); + } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { + // Check if configuration change was an mcc/mnc change which would affect app resources + // and we would need to clear out the labels in all apps/workspace. Same handling as + // above for ACTION_LOCALE_CHANGED + Configuration currentConfig = context.getResources().getConfiguration(); + if (mPreviousConfigMcc != currentConfig.mcc) { + Log.d(TAG, "Reload apps on config change. curr_mcc:" + + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc); + forceReload(); + } + // Update previousConfig + mPreviousConfigMcc = currentConfig.mcc; + } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || + SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) { + if (mCallbacks != null) { + Callbacks callbacks = mCallbacks.get(); + if (callbacks != null) { + callbacks.bindSearchablesChanged(); + } + } + } + } + + private void forceReload() { + resetLoadedState(true, true); + + // 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. + startLoaderFromBackground(); + } + + public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) { + synchronized (mLock) { + // Stop any existing loaders first, so they don't set mAllAppsLoaded or + // mWorkspaceLoaded to true later + stopLoaderLocked(); + if (resetAllAppsLoaded) mAllAppsLoaded = false; + if (resetWorkspaceLoaded) mWorkspaceLoaded = false; + } + } + + /** + * 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(false); + } + } + + // If there is already a loader task running, tell it to stop. + // returns true if isLaunching() was true on the old task + private boolean stopLoaderLocked() { + boolean isLaunching = false; + LoaderTask oldTask = mLoaderTask; + if (oldTask != null) { + if (oldTask.isLaunching()) { + isLaunching = true; + } + oldTask.stopLocked(); + } + return isLaunching; + } + + public void startLoader(boolean isLaunching) { + synchronized (mLock) { + if (DEBUG_LOADERS) { + Log.d(TAG, "startLoader isLaunching=" + isLaunching); + } + + // Don't bother to start the thread if we know it's not going to do anything + if (mCallbacks != null && mCallbacks.get() != null) { + // If there is already one running, tell it to stop. + // also, don't downgrade isLaunching if we're already running + isLaunching = isLaunching || stopLoaderLocked(); + mLoaderTask = new LoaderTask(mApp, isLaunching); + sWorkerThread.setPriority(Thread.NORM_PRIORITY); + sWorker.post(mLoaderTask); + } + } + } + + public void stopLoader() { + synchronized (mLock) { + if (mLoaderTask != null) { + mLoaderTask.stopLocked(); + } + } + } + + public boolean isAllAppsLoaded() { + return mAllAppsLoaded; + } + + boolean isLoadingWorkspace() { + synchronized (mLock) { + if (mLoaderTask != null) { + return mLoaderTask.isLoadingWorkspace(); + } + } + return false; + } + + /** + * Runnable for the thread that loads the contents of the launcher: + * - workspace icons + * - widgets + * - all apps icons + */ + private class LoaderTask implements Runnable { + private Context mContext; + private Thread mWaitThread; + private boolean mIsLaunching; + private boolean mIsLoadingAndBindingWorkspace; + private boolean mStopped; + private boolean mLoadAndBindStepFinished; + private HashMap mLabelCache; + + LoaderTask(Context context, boolean isLaunching) { + mContext = context; + mIsLaunching = isLaunching; + mLabelCache = new HashMap(); + } + + boolean isLaunching() { + return mIsLaunching; + } + + boolean isLoadingWorkspace() { + return mIsLoadingAndBindingWorkspace; + } + + private void loadAndBindWorkspace() { + mIsLoadingAndBindingWorkspace = true; + + // Load the workspace + if (DEBUG_LOADERS) { + Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); + } + + if (!mWorkspaceLoaded) { + loadWorkspace(); + synchronized (LoaderTask.this) { + if (mStopped) { + return; + } + mWorkspaceLoaded = true; + } + } + + // Bind the workspace + bindWorkspace(); + } + + private void waitForIdle() { + // Wait until the either we're stopped or the other threads are done. + // This way we don't start loading all apps until the workspace has settled + // down. + synchronized (LoaderTask.this) { + final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + + mHandler.postIdle(new Runnable() { + public void run() { + synchronized (LoaderTask.this) { + mLoadAndBindStepFinished = true; + if (DEBUG_LOADERS) { + Log.d(TAG, "done with previous binding step"); + } + LoaderTask.this.notify(); + } + } + }); + + while (!mStopped && !mLoadAndBindStepFinished) { + try { + this.wait(); + } catch (InterruptedException ex) { + // Ignore + } + } + if (DEBUG_LOADERS) { + Log.d(TAG, "waited " + + (SystemClock.uptimeMillis()-workspaceWaitTime) + + "ms for previous step to finish binding"); + } + } + } + + public void run() { + // Optimize for end-user experience: if the Launcher is up and // running with the + // All Apps interface in the foreground, load All Apps first. Otherwise, load the + // workspace first (default). + final Callbacks cbk = mCallbacks.get(); + final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true; + + keep_running: { + // Elevate priority when Home launches for the first time to avoid + // starving at boot time. Staring at a blank home is not cool. + synchronized (mLock) { + if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + + (mIsLaunching ? "DEFAULT" : "BACKGROUND")); + 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(); + } else { + if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); + loadAndBindAllApps(); + } + + if (mStopped) { + break keep_running; + } + + // Whew! Hard work done. Slow us down, and wait until the UI thread has + // settled down. + synchronized (mLock) { + if (mIsLaunching) { + if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); + android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + } + waitForIdle(); + + // second step + if (loadWorkspaceFirst) { + if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); + loadAndBindAllApps(); + } else { + if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace"); + loadAndBindWorkspace(); + } + + // Restore the default thread priority after we are done loading items + synchronized (mLock) { + android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + } + } + + + // Update the saved icons if necessary + if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); + for (Object key : sDbIconCache.keySet()) { + updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key)); + } + sDbIconCache.clear(); + + // Clear out this reference, otherwise we end up holding it until all of the + // callback runnables are done. + mContext = null; + + synchronized (mLock) { + // If we are still the last one to be scheduled, remove ourselves. + if (mLoaderTask == this) { + mLoaderTask = null; + } + } + } + + public void stopLocked() { + synchronized (LoaderTask.this) { + mStopped = true; + this.notify(); + } + } + + /** + * Gets the callbacks object. If we've been stopped, or if the launcher object + * has somehow been garbage collected, return null instead. Pass in the Callbacks + * object that was around when the deferred message was scheduled, and if there's + * a new Callbacks object around then also return null. This will save us from + * calling onto it with data that will be ignored. + */ + Callbacks tryGetCallbacks(Callbacks oldCallbacks) { + synchronized (mLock) { + if (mStopped) { + return null; + } + + if (mCallbacks == null) { + return null; + } + + final Callbacks callbacks = mCallbacks.get(); + if (callbacks != oldCallbacks) { + return null; + } + if (callbacks == null) { + Log.w(TAG, "no mCallbacks"); + return null; + } + + return callbacks; + } + } + + // check & update map of what's occupied; used to discard overlapping/invalid items + private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) { + int containerIndex = item.screen; + if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + // Return early if we detect that an item is under the hotseat button + if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) { + return false; + } + + // We use the last index to refer to the hotseat and the screen as the rank, so + // test and update the occupied state accordingly + if (occupied[Launcher.SCREEN_COUNT][item.screen][0] != null) { + Log.e(TAG, "Error loading shortcut into hotseat " + item + + " into position (" + item.screen + ":" + item.cellX + "," + item.cellY + + ") occupied by " + occupied[Launcher.SCREEN_COUNT][item.screen][0]); + return false; + } else { + occupied[Launcher.SCREEN_COUNT][item.screen][0] = item; + return true; + } + } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { + // Skip further checking if it is not the hotseat or workspace container + return true; + } + + // Check if any workspace icons overlap with each other + for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { + for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { + if (occupied[containerIndex][x][y] != null) { + Log.e(TAG, "Error loading shortcut " + item + + " into cell (" + containerIndex + "-" + item.screen + ":" + + x + "," + y + + ") occupied by " + + occupied[containerIndex][x][y]); + return false; + } + } + } + for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { + for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { + occupied[containerIndex][x][y] = item; + } + } + + return true; + } + + private void loadWorkspace() { + final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + + final Context context = mContext; + final ContentResolver contentResolver = context.getContentResolver(); + final PackageManager manager = context.getPackageManager(); + final AppWidgetManager widgets = AppWidgetManager.getInstance(context); + final boolean isSafeMode = manager.isSafeMode(); + + // Make sure the default workspace is loaded, if needed + mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(); + + sWorkspaceItems.clear(); + sAppWidgets.clear(); + sFolders.clear(); + sItemsIdMap.clear(); + sDbIconCache.clear(); + + final ArrayList itemsToRemove = new ArrayList(); + + final Cursor c = contentResolver.query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); + + // +1 for the hotseat (it can be larger than the workspace) + // Load workspace in reverse order to ensure that latest items are loaded first (and + // before any earlier duplicates) + final ItemInfo occupied[][][] = + new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; + + try { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.INTENT); + final int titleIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.TITLE); + final int iconTypeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_TYPE); + final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_RESOURCE); + final int containerIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.CONTAINER); + final int itemTypeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ITEM_TYPE); + final int appWidgetIdIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.APPWIDGET_ID); + 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); + //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); + //final int displayModeIndex = c.getColumnIndexOrThrow( + // LauncherSettings.Favorites.DISPLAY_MODE); + + ShortcutInfo info; + String intentDescription; + LauncherAppWidgetInfo appWidgetInfo; + int container; + long id; + Intent intent; + + while (!mStopped && c.moveToNext()) { + try { + int itemType = c.getInt(itemTypeIndex); + + switch (itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + intentDescription = c.getString(intentIndex); + try { + intent = Intent.parseUri(intentDescription, 0); + } catch (URISyntaxException e) { + continue; + } + + if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + info = getShortcutInfo(manager, intent, context, c, iconIndex, + titleIndex, mLabelCache); + } else { + info = getShortcutInfo(c, context, iconTypeIndex, + iconPackageIndex, iconResourceIndex, iconIndex, + titleIndex); + + // App shortcuts that used to be automatically added to Launcher + // didn't always have the correct intent flags set, so do that here + if (intent.getAction() != null && + intent.getCategories() != null && + intent.getAction().equals(Intent.ACTION_MAIN) && + intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } + } + + if (info != null) { + info.intent = intent; + info.id = c.getLong(idIndex); + container = c.getInt(containerIndex); + info.container = container; + info.screen = c.getInt(screenIndex); + info.cellX = c.getInt(cellXIndex); + info.cellY = c.getInt(cellYIndex); + + // check & update map of what's occupied + if (!checkItemPlacement(occupied, info)) { + break; + } + + switch (container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: + sWorkspaceItems.add(info); + break; + default: + // Item is in a user folder + FolderInfo folderInfo = + findOrMakeFolder(sFolders, container); + folderInfo.add(info); + break; + } + sItemsIdMap.put(info.id, info); + + // now that we've loaded everthing re-save it with the + // icon in case it disappears somehow. + queueIconToBeChecked(sDbIconCache, info, c, iconIndex); + } else { + // Failed to load the shortcut, probably because the + // activity manager couldn't resolve it (maybe the app + // was uninstalled), or the db row was somehow screwed up. + // Delete it. + id = c.getLong(idIndex); + Log.e(TAG, "Error loading shortcut " + id + ", removing it"); + contentResolver.delete(LauncherSettings.Favorites.getContentUri( + id, false), null, null); + } + break; + + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + id = c.getLong(idIndex); + FolderInfo folderInfo = findOrMakeFolder(sFolders, id); + + folderInfo.title = c.getString(titleIndex); + folderInfo.id = id; + container = c.getInt(containerIndex); + folderInfo.container = container; + folderInfo.screen = c.getInt(screenIndex); + folderInfo.cellX = c.getInt(cellXIndex); + folderInfo.cellY = c.getInt(cellYIndex); + + // check & update map of what's occupied + if (!checkItemPlacement(occupied, folderInfo)) { + break; + } + switch (container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: + sWorkspaceItems.add(folderInfo); + break; + } + + sItemsIdMap.put(folderInfo.id, folderInfo); + sFolders.put(folderInfo.id, folderInfo); + break; + + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + // Read all Launcher-specific widget details + int appWidgetId = c.getInt(appWidgetIdIndex); + id = c.getLong(idIndex); + + final AppWidgetProviderInfo provider = + widgets.getAppWidgetInfo(appWidgetId); + + if (!isSafeMode && (provider == null || provider.provider == null || + provider.provider.getPackageName() == null)) { + String log = "Deleting widget that isn't installed anymore: id=" + + id + " appWidgetId=" + appWidgetId; + Log.e(TAG, log); + Launcher.sDumpLogs.add(log); + itemsToRemove.add(id); + } else { + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, + provider.provider); + appWidgetInfo.id = id; + appWidgetInfo.screen = c.getInt(screenIndex); + appWidgetInfo.cellX = c.getInt(cellXIndex); + appWidgetInfo.cellY = c.getInt(cellYIndex); + appWidgetInfo.spanX = c.getInt(spanXIndex); + appWidgetInfo.spanY = c.getInt(spanYIndex); + int[] minSpan = Launcher.getMinSpanForWidget(context, provider); + appWidgetInfo.minSpanX = minSpan[0]; + appWidgetInfo.minSpanY = minSpan[1]; + + container = c.getInt(containerIndex); + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && + container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + Log.e(TAG, "Widget found where container " + + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); + continue; + } + appWidgetInfo.container = c.getInt(containerIndex); + + // check & update map of what's occupied + if (!checkItemPlacement(occupied, appWidgetInfo)) { + break; + } + sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); + sAppWidgets.add(appWidgetInfo); + } + break; + } + } catch (Exception e) { + Log.w(TAG, "Desktop items loading interrupted:", e); + } + } + } finally { + c.close(); + } + + if (itemsToRemove.size() > 0) { + ContentProviderClient client = contentResolver.acquireContentProviderClient( + LauncherSettings.Favorites.CONTENT_URI); + // Remove dead items + for (long id : itemsToRemove) { + if (DEBUG_LOADERS) { + Log.d(TAG, "Removed id = " + id); + } + // Don't notify content observers + try { + client.delete(LauncherSettings.Favorites.getContentUri(id, false), + null, null); + } catch (RemoteException e) { + Log.w(TAG, "Could not remove id = " + id); + } + } + } + + if (DEBUG_LOADERS) { + Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); + Log.d(TAG, "workspace layout: "); + 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 < mCellCountX; x++) { + line += ((occupied[s][x][y] != null) ? "#" : "."); + } + } + Log.d(TAG, "[ " + line + " ]"); + } + } + } + + /** + * Read everything out of our database. + */ + private void bindWorkspace() { + final long t = SystemClock.uptimeMillis(); + + // Don't use these two variables in any of the callback runnables. + // Otherwise we hold a reference to them. + final Callbacks oldCallbacks = mCallbacks.get(); + if (oldCallbacks == null) { + // This launcher has exited and nobody bothered to tell us. Just bail. + Log.w(TAG, "LoaderTask running with no launcher"); + return; + } + + // Get the list of workspace items to load and unbind the existing ShortcutInfos + // before we call startBinding() below. + final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); + final ArrayList tmpWorkspaceItems = unbindWorkspaceItemsOnMainThread(); + // Order the items for loading as follows: current workspace, hotseat, everything else + Collections.sort(tmpWorkspaceItems, new Comparator() { + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + int cellCountX = LauncherModel.getCellCountX(); + int cellCountY = LauncherModel.getCellCountY(); + int screenOffset = cellCountX * cellCountY; + int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat + long lr = (lhs.container * containerOffset + lhs.screen * screenOffset + + lhs.cellY * cellCountX + lhs.cellX); + long rr = (rhs.container * containerOffset + rhs.screen * screenOffset + + rhs.cellY * cellCountX + rhs.cellX); + return (int) (lr - rr); + } + }); + // Precondition: the items are ordered by page, screen + final ArrayList workspaceItems = new ArrayList(); + for (ItemInfo ii : tmpWorkspaceItems) { + // Prepend the current items, hotseat items, append everything else + if (ii.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && + ii.screen == currentScreen) { + workspaceItems.add(0, ii); + } else if (ii.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + workspaceItems.add(0, ii); + } else { + workspaceItems.add(ii); + } + } + + // Tell the workspace that we're about to start firing items at it + mHandler.post(new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.startBinding(); + } + } + }); + + // Add the items to the workspace. + int N = workspaceItems.size(); + for (int i=0; i folders = new HashMap(sFolders); + mHandler.post(new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.bindFolders(folders); + } + } + }); + // Wait until the queue goes empty. + mHandler.post(new Runnable() { + public void run() { + if (DEBUG_LOADERS) { + Log.d(TAG, "Going to start binding widgets soon."); + } + } + }); + // Bind the widgets, one at a time. + // WARNING: this is calling into the workspace from the background thread, + // but since getCurrentScreen() just returns the int, we should be okay. This + // is just a hint for the order, and if it's wrong, we'll be okay. + // TODO: instead, we should have that push the current screen into here. + N = sAppWidgets.size(); + // once for the current screen + for (int i=0; i list + = (ArrayList) mAllAppsList.data.clone(); + mHandler.post(new Runnable() { + public void run() { + final long t = SystemClock.uptimeMillis(); + final Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + callbacks.bindAllApplications(list); + } + if (DEBUG_LOADERS) { + Log.d(TAG, "bound all " + list.size() + " apps from cache in " + + (SystemClock.uptimeMillis()-t) + "ms"); + } + } + }); + + } + + private void loadAllAppsByBatch() { + final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + + // Don't use these two variables in any of the callback runnables. + // Otherwise we hold a reference to them. + final Callbacks oldCallbacks = mCallbacks.get(); + if (oldCallbacks == null) { + // This launcher has exited and nobody bothered to tell us. Just bail. + Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)"); + return; + } + + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + final PackageManager packageManager = mContext.getPackageManager(); + List apps = null; + + int N = Integer.MAX_VALUE; + + int startIndex; + int i=0; + int batchSize = -1; + while (i < N && !mStopped) { + if (i == 0) { + mAllAppsList.clear(); + final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + apps = packageManager.queryIntentActivities(mainIntent, 0); + if (DEBUG_LOADERS) { + Log.d(TAG, "queryIntentActivities took " + + (SystemClock.uptimeMillis()-qiaTime) + "ms"); + } + if (apps == null) { + return; + } + N = apps.size(); + if (DEBUG_LOADERS) { + Log.d(TAG, "queryIntentActivities got " + N + " apps"); + } + if (N == 0) { + // There are no apps?!? + return; + } + if (mBatchSize == 0) { + batchSize = N; + } else { + batchSize = mBatchSize; + } + + final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + Collections.sort(apps, + new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache)); + if (DEBUG_LOADERS) { + Log.d(TAG, "sort took " + + (SystemClock.uptimeMillis()-sortTime) + "ms"); + } + } + + final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; + + startIndex = i; + for (int j=0; i added = mAllAppsList.added; + mAllAppsList.added = new ArrayList(); + + mHandler.post(new Runnable() { + public void run() { + final long t = SystemClock.uptimeMillis(); + if (callbacks != null) { + if (first) { + callbacks.bindAllApplications(added); + } else { + callbacks.bindAppsAdded(added); + } + if (DEBUG_LOADERS) { + Log.d(TAG, "bound " + added.size() + " apps in " + + (SystemClock.uptimeMillis() - t) + "ms"); + } + } else { + Log.i(TAG, "not binding apps: no Launcher activity"); + } + } + }); + + if (DEBUG_LOADERS) { + Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in " + + (SystemClock.uptimeMillis()-t2) + "ms"); + } + + if (mAllAppsLoadDelay > 0 && i < N) { + try { + if (DEBUG_LOADERS) { + Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); + } + Thread.sleep(mAllAppsLoadDelay); + } catch (InterruptedException exc) { } + } + } + + if (DEBUG_LOADERS) { + Log.d(TAG, "cached all " + N + " apps in " + + (SystemClock.uptimeMillis()-t) + "ms" + + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); + } + } + + public void dumpState() { + Log.d(TAG, "mLoaderTask.mContext=" + mContext); + Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread); + Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); + Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); + Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); + Log.d(TAG, "mItems size=" + sWorkspaceItems.size()); + } + } + + void enqueuePackageUpdated(PackageUpdatedTask task) { + sWorker.post(task); + } + + private class PackageUpdatedTask implements Runnable { + int mOp; + String[] mPackages; + + public static final int OP_NONE = 0; + public static final int OP_ADD = 1; + public static final int OP_UPDATE = 2; + public static final int OP_REMOVE = 3; // uninstlled + public static final int OP_UNAVAILABLE = 4; // external media unmounted + + + public PackageUpdatedTask(int op, String[] packages) { + mOp = op; + mPackages = packages; + } + + public void run() { + final Context context = mApp; + + final String[] packages = mPackages; + final int N = packages.length; + switch (mOp) { + case OP_ADD: + for (int i=0; i added = null; + ArrayList removed = null; + ArrayList modified = null; + + if (mAllAppsList.added.size() > 0) { + added = mAllAppsList.added; + mAllAppsList.added = new ArrayList(); + } + if (mAllAppsList.removed.size() > 0) { + removed = mAllAppsList.removed; + mAllAppsList.removed = new ArrayList(); + for (ApplicationInfo info: removed) { + mIconCache.remove(info.intent.getComponent()); + } + } + if (mAllAppsList.modified.size() > 0) { + modified = mAllAppsList.modified; + mAllAppsList.modified = new ArrayList(); + } + + final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; + if (callbacks == null) { + Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); + return; + } + + if (added != null) { + final ArrayList addedFinal = added; + mHandler.post(new Runnable() { + public void run() { + Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; + if (callbacks == cb && cb != null) { + callbacks.bindAppsAdded(addedFinal); + } + } + }); + } + if (modified != null) { + final ArrayList modifiedFinal = modified; + mHandler.post(new Runnable() { + public void run() { + Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; + if (callbacks == cb && cb != null) { + callbacks.bindAppsUpdated(modifiedFinal); + } + } + }); + } + if (removed != null) { + final boolean permanent = mOp != OP_UNAVAILABLE; + final ArrayList removedFinal = removed; + mHandler.post(new Runnable() { + public void run() { + Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; + if (callbacks == cb && cb != null) { + callbacks.bindAppsRemoved(removedFinal, permanent); + } + } + }); + } + + mHandler.post(new Runnable() { + @Override + public void run() { + Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; + if (callbacks == cb && cb != null) { + callbacks.bindPackagesUpdated(); + } + } + }); + } + } + + /** + * Returns all the Workspace ShortcutInfos associated with a particular package. + * @param intent + * @return + */ + ArrayList getShortcutInfosForPackage(String packageName) { + ArrayList infos = new ArrayList(); + for (ItemInfo i : sWorkspaceItems) { + if (i instanceof ShortcutInfo) { + ShortcutInfo info = (ShortcutInfo) i; + if (packageName.equals(info.getPackageName())) { + infos.add(info); + } + } + } + return infos; + } + + /** + * This is called from the code that adds shortcuts from the intent receiver. This + * doesn't have a Cursor, but + */ + public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { + return getShortcutInfo(manager, intent, context, null, -1, -1, null); + } + + /** + * Make an ShortcutInfo object for a shortcut that is an application. + * + * If c is not null, then it will be used to fill in missing data like the title and icon. + */ + public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, + Cursor c, int iconIndex, int titleIndex, HashMap labelCache) { + Bitmap icon = null; + final ShortcutInfo info = new ShortcutInfo(); + + ComponentName componentName = intent.getComponent(); + if (componentName == null) { + return null; + } + + try { + PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0); + if (!pi.applicationInfo.enabled) { + // If we return null here, the corresponding item will be removed from the launcher + // db and will not appear in the workspace. + return null; + } + } catch (NameNotFoundException e) { + Log.d(TAG, "getPackInfo failed for package " + componentName.getPackageName()); + } + + // TODO: See if the PackageManager knows about this case. If it doesn't + // then return null & delete this. + + // the resource -- This may implicitly give us back the fallback icon, + // but don't worry about that. All we're doing with usingFallbackIcon is + // to avoid saving lots of copies of that in the database, and most apps + // have icons anyway. + + // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and + // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info + // via resolveActivity(). + ResolveInfo resolveInfo = null; + ComponentName oldComponent = intent.getComponent(); + Intent newIntent = new Intent(intent.getAction(), null); + newIntent.addCategory(Intent.CATEGORY_LAUNCHER); + newIntent.setPackage(oldComponent.getPackageName()); + List infos = manager.queryIntentActivities(newIntent, 0); + for (ResolveInfo i : infos) { + ComponentName cn = new ComponentName(i.activityInfo.packageName, + i.activityInfo.name); + if (cn.equals(oldComponent)) { + resolveInfo = i; + } + } + if (resolveInfo == null) { + resolveInfo = manager.resolveActivity(intent, 0); + } + if (resolveInfo != null) { + icon = mIconCache.getIcon(componentName, resolveInfo, labelCache); + } + // the db + if (icon == null) { + if (c != null) { + icon = getIconFromCursor(c, iconIndex, context); + } + } + // the fallback icon + if (icon == null) { + icon = getFallbackIcon(); + info.usingFallbackIcon = true; + } + info.setIcon(icon); + + // from the resource + if (resolveInfo != null) { + ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo); + if (labelCache != null && labelCache.containsKey(key)) { + info.title = labelCache.get(key); + } else { + info.title = resolveInfo.activityInfo.loadLabel(manager); + if (labelCache != null) { + labelCache.put(key, info.title); + } + } + } + // from the db + if (info.title == null) { + if (c != null) { + info.title = c.getString(titleIndex); + } + } + // fall back to the class name of the activity + if (info.title == null) { + info.title = componentName.getClassName(); + } + info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + return info; + } + + /** + * Make an ShortcutInfo object for a shortcut that isn't an application. + */ + private ShortcutInfo getShortcutInfo(Cursor c, Context context, + int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, + int titleIndex) { + + Bitmap icon = null; + final ShortcutInfo info = new ShortcutInfo(); + info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; + + // TODO: If there's an explicit component and we can't install that, delete it. + + info.title = c.getString(titleIndex); + + int iconType = c.getInt(iconTypeIndex); + switch (iconType) { + case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: + String packageName = c.getString(iconPackageIndex); + String resourceName = c.getString(iconResourceIndex); + PackageManager packageManager = context.getPackageManager(); + info.customIcon = false; + // the resource + try { + Resources resources = packageManager.getResourcesForApplication(packageName); + if (resources != null) { + final int id = resources.getIdentifier(resourceName, null, null); + icon = Utilities.createIconBitmap( + mIconCache.getFullResIcon(resources, id), context); + } + } catch (Exception e) { + // drop this. we have other places to look for icons + } + // the db + if (icon == null) { + icon = getIconFromCursor(c, iconIndex, context); + } + // the fallback icon + if (icon == null) { + icon = getFallbackIcon(); + info.usingFallbackIcon = true; + } + break; + case LauncherSettings.Favorites.ICON_TYPE_BITMAP: + icon = getIconFromCursor(c, iconIndex, context); + if (icon == null) { + icon = getFallbackIcon(); + info.customIcon = false; + info.usingFallbackIcon = true; + } else { + info.customIcon = true; + } + break; + default: + icon = getFallbackIcon(); + info.usingFallbackIcon = true; + info.customIcon = false; + break; + } + info.setIcon(icon); + return info; + } + + Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) { + @SuppressWarnings("all") // suppress dead code warning + final boolean debug = false; + if (debug) { + Log.d(TAG, "getIconFromCursor app=" + + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); + } + byte[] data = c.getBlob(iconIndex); + try { + return Utilities.createIconBitmap( + BitmapFactory.decodeByteArray(data, 0, data.length), context); + } catch (Exception e) { + return null; + } + } + + ShortcutInfo addShortcut(Context context, Intent data, long container, int screen, + int cellX, int cellY, boolean notify) { + final ShortcutInfo info = infoFromShortcutIntent(context, data, null); + if (info == null) { + return null; + } + addItemToDatabase(context, info, container, screen, cellX, cellY, notify); + + return info; + } + + /** + * Attempts to find an AppWidgetProviderInfo that matches the given component. + */ + AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, + ComponentName component) { + List 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 resolveWidgetsForMimeType(Context context, String mimeType) { + final PackageManager packageManager = context.getPackageManager(); + final List supportedConfigurationActivities = + new ArrayList(); + + 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 widgets = + AppWidgetManager.getInstance(context).getInstalledProviders(); + final HashMap configurationComponentToWidget = + new HashMap(); + 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 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); + + if (intent == null) { + // If the intent is null, we can't construct a valid ShortcutInfo, so we return null + Log.e(TAG, "Can't construct ShorcutInfo with null intent"); + return null; + } + + Bitmap icon = null; + boolean customIcon = false; + ShortcutIconResource iconResource = null; + + if (bitmap != null && bitmap instanceof Bitmap) { + icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context); + customIcon = true; + } else { + Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); + if (extra != null && extra instanceof ShortcutIconResource) { + try { + iconResource = (ShortcutIconResource) extra; + final PackageManager packageManager = context.getPackageManager(); + Resources resources = packageManager.getResourcesForApplication( + iconResource.packageName); + final int id = resources.getIdentifier(iconResource.resourceName, null, null); + icon = Utilities.createIconBitmap( + mIconCache.getFullResIcon(resources, id), context); + } catch (Exception e) { + Log.w(TAG, "Could not load shortcut icon: " + extra); + } + } + } + + final ShortcutInfo info = new ShortcutInfo(); + + if (icon == null) { + if (fallbackIcon != null) { + icon = fallbackIcon; + } else { + icon = getFallbackIcon(); + info.usingFallbackIcon = true; + } + } + info.setIcon(icon); + + info.title = name; + info.intent = intent; + info.customIcon = customIcon; + info.iconResource = iconResource; + + return info; + } + + boolean queueIconToBeChecked(HashMap cache, ShortcutInfo info, Cursor c, + int iconIndex) { + // If apps can't be on SD, don't even bother. + if (!mAppsCanBeOnExternalStorage) { + return false; + } + // If this icon doesn't have a custom icon, check to see + // what's stored in the DB, and if it doesn't match what + // we're going to show, store what we are going to show back + // into the DB. We do this so when we're loading, if the + // package manager can't find an icon (for example because + // the app is on SD) then we can use that instead. + if (!info.customIcon && !info.usingFallbackIcon) { + cache.put(info, c.getBlob(iconIndex)); + return true; + } + return false; + } + void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) { + boolean needSave = false; + try { + if (data != null) { + Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); + Bitmap loaded = info.getIcon(mIconCache); + needSave = !saved.sameAs(loaded); + } else { + needSave = true; + } + } catch (Exception e) { + needSave = true; + } + if (needSave) { + Log.d(TAG, "going to save icon bitmap for info=" + info); + // This is slower than is ideal, but this only happens once + // or when the app is updated with a new icon. + updateItemInDatabase(context, info); + } + } + + /** + * Return an existing FolderInfo object if we have encountered this ID previously, + * or make a new one. + */ + private static FolderInfo findOrMakeFolder(HashMap folders, long id) { + // See if a placeholder was created for us already + FolderInfo folderInfo = folders.get(id); + if (folderInfo == null) { + // No placeholder -- create a new instance + folderInfo = new FolderInfo(); + folders.put(id, folderInfo); + } + return folderInfo; + } + + private static final Collator sCollator = Collator.getInstance(); + public static final Comparator APP_NAME_COMPARATOR + = new Comparator() { + public final int compare(ApplicationInfo a, ApplicationInfo b) { + int result = sCollator.compare(a.title.toString(), b.title.toString()); + if (result == 0) { + result = a.componentName.compareTo(b.componentName); + } + return result; + } + }; + public static final Comparator APP_INSTALL_TIME_COMPARATOR + = new Comparator() { + public final int compare(ApplicationInfo a, ApplicationInfo b) { + if (a.firstInstallTime < b.firstInstallTime) return 1; + if (a.firstInstallTime > b.firstInstallTime) return -1; + return 0; + } + }; + public static final Comparator WIDGET_NAME_COMPARATOR + = new Comparator() { + public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) { + return sCollator.compare(a.label.toString(), b.label.toString()); + } + }; + static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) { + if (info.activityInfo != null) { + return new ComponentName(info.activityInfo.packageName, info.activityInfo.name); + } else { + return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); + } + } + public static class ShortcutNameComparator implements Comparator { + private PackageManager mPackageManager; + private HashMap mLabelCache; + ShortcutNameComparator(PackageManager pm) { + mPackageManager = pm; + mLabelCache = new HashMap(); + } + ShortcutNameComparator(PackageManager pm, HashMap labelCache) { + mPackageManager = pm; + mLabelCache = labelCache; + } + public final int compare(ResolveInfo a, ResolveInfo b) { + CharSequence labelA, labelB; + ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a); + ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b); + if (mLabelCache.containsKey(keyA)) { + labelA = mLabelCache.get(keyA); + } else { + labelA = a.loadLabel(mPackageManager).toString(); + + mLabelCache.put(keyA, labelA); + } + if (mLabelCache.containsKey(keyB)) { + labelB = mLabelCache.get(keyB); + } else { + labelB = b.loadLabel(mPackageManager).toString(); + + mLabelCache.put(keyB, labelB); + } + return sCollator.compare(labelA, labelB); + } + }; + public static class WidgetAndShortcutNameComparator implements Comparator { + private PackageManager mPackageManager; + private HashMap mLabelCache; + WidgetAndShortcutNameComparator(PackageManager pm) { + mPackageManager = pm; + mLabelCache = new HashMap(); + } + public final int compare(Object a, Object b) { + String labelA, labelB; + if (mLabelCache.containsKey(a)) { + labelA = mLabelCache.get(a); + } else { + labelA = (a instanceof AppWidgetProviderInfo) ? + ((AppWidgetProviderInfo) a).label : + ((ResolveInfo) a).loadLabel(mPackageManager).toString(); + mLabelCache.put(a, labelA); + } + if (mLabelCache.containsKey(b)) { + labelB = mLabelCache.get(b); + } else { + labelB = (b instanceof AppWidgetProviderInfo) ? + ((AppWidgetProviderInfo) b).label : + ((ResolveInfo) b).loadLabel(mPackageManager).toString(); + mLabelCache.put(b, labelB); + } + return sCollator.compare(labelA, labelB); + } + }; + + public void dumpState() { + Log.d(TAG, "mCallbacks=" + mCallbacks); + ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); + ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); + ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); + ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); + if (mLoaderTask != null) { + mLoaderTask.dumpState(); + } else { + Log.d(TAG, "mLoaderTask=null"); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherProvider.java b/src/com/cyanogenmod/trebuchet/LauncherProvider.java new file mode 100644 index 000000000..4cdd135e6 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherProvider.java @@ -0,0 +1,1178 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.app.SearchManager; +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.database.sqlite.SQLiteStatement; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.LauncherSettings.Favorites; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +public class LauncherProvider extends ContentProvider { + private static final String TAG = "Launcher.LauncherProvider"; + private static final boolean LOGD = false; + + private static final String DATABASE_NAME = "launcher.db"; + + private static final int DATABASE_VERSION = 12; + + static final String AUTHORITY = "com.cyanogenmod.trebuchet.settings"; + + static final String TABLE_FAVORITES = "favorites"; + static final String PARAMETER_NOTIFY = "notify"; + static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED = + "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED"; + + private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = + "com.cyanogenmod.trebuchet.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; + + /** + * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when + * {@link AppWidgetHost#deleteHost()} is called during database creation. + * Use this to recall {@link AppWidgetHost#startListening()} if needed. + */ + static final Uri CONTENT_APPWIDGET_RESET_URI = + Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); + + private DatabaseHelper mOpenHelper; + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + ((LauncherApplication) getContext()).setLauncherProvider(this); + return true; + } + + @Override + public String getType(Uri uri) { + SqlArguments args = new SqlArguments(uri, null, null); + if (TextUtils.isEmpty(args.where)) { + return "vnd.android.cursor.dir/" + args.table; + } else { + return "vnd.android.cursor.item/" + args.table; + } + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(args.table); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); + result.setNotificationUri(getContext().getContentResolver(), uri); + + return result; + } + + private static long dbInsertAndCheck(DatabaseHelper helper, + SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { + if (!values.containsKey(LauncherSettings.Favorites._ID)) { + throw new RuntimeException("Error: attempting to add item without specifying an id"); + } + return db.insert(table, nullColumnHack, values); + } + + private static void deleteId(SQLiteDatabase db, long id) { + Uri uri = LauncherSettings.Favorites.getContentUri(id, false); + SqlArguments args = new SqlArguments(uri, null, null); + db.delete(args.table, args.where, args.args); + } + + @Override + public Uri insert(Uri uri, ContentValues initialValues) { + SqlArguments args = new SqlArguments(uri); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); + if (rowId <= 0) return null; + + uri = ContentUris.withAppendedId(uri, rowId); + sendNotify(uri); + + return uri; + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + SqlArguments args = new SqlArguments(uri); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + int numValues = values.length; + for (int i = 0; i < numValues; i++) { + if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { + return 0; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + sendNotify(uri); + return values.length; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count = db.delete(args.table, args.where, args.args); + if (count > 0) sendNotify(uri); + + return count; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count = db.update(args.table, values, args.where, args.args); + if (count > 0) sendNotify(uri); + + return count; + } + + private void sendNotify(Uri uri) { + String notify = uri.getQueryParameter(PARAMETER_NOTIFY); + if (notify == null || "true".equals(notify)) { + getContext().getContentResolver().notifyChange(uri, null); + } + } + + public long generateNewId() { + return mOpenHelper.generateNewId(); + } + + synchronized public void loadDefaultFavoritesIfNecessary() { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); + if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) { + // Populate favorites table with initial favorites + SharedPreferences.Editor editor = sp.edit(); + editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED); + mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace); + editor.commit(); + } + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private static final String TAG_FAVORITES = "favorites"; + private static final String TAG_FAVORITE = "favorite"; + private static final String TAG_CLOCK = "clock"; + private static final String TAG_SEARCH = "search"; + private static final String TAG_APPWIDGET = "appwidget"; + private static final String TAG_SHORTCUT = "shortcut"; + private static final String TAG_FOLDER = "folder"; + private static final String TAG_EXTRA = "extra"; + + private final Context mContext; + private final AppWidgetHost mAppWidgetHost; + private long mMaxId = -1; + + DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mContext = context; + mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); + + // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from + // the DB here + if (mMaxId == -1) { + mMaxId = initializeMaxId(getWritableDatabase()); + } + } + + /** + * Send notification that we've deleted the {@link AppWidgetHost}, + * probably as part of the initial database creation. The receiver may + * want to re-call {@link AppWidgetHost#startListening()} to ensure + * callbacks are correctly set. + */ + private void sendAppWidgetResetNotify() { + final ContentResolver resolver = mContext.getContentResolver(); + resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); + } + + @Override + public void onCreate(SQLiteDatabase db) { + if (LOGD) Log.d(TAG, "creating new launcher database"); + + mMaxId = 1; + + db.execSQL("CREATE TABLE favorites (" + + "_id INTEGER PRIMARY KEY," + + "title TEXT," + + "intent TEXT," + + "container INTEGER," + + "screen INTEGER," + + "cellX INTEGER," + + "cellY INTEGER," + + "spanX INTEGER," + + "spanY INTEGER," + + "itemType INTEGER," + + "appWidgetId INTEGER NOT NULL DEFAULT -1," + + "isShortcut INTEGER," + + "iconType INTEGER," + + "iconPackage TEXT," + + "iconResource TEXT," + + "icon BLOB," + + "uri TEXT," + + "displayMode INTEGER" + + ");"); + + // Database was just created, so wipe any previous widgets + if (mAppWidgetHost != null) { + mAppWidgetHost.deleteHost(); + sendAppWidgetResetNotify(); + } + + if (!convertDatabase(db)) { + // Set a shared pref so that we know we need to load the default workspace later + setFlagToLoadDefaultWorkspaceLater(); + } + } + + private void setFlagToLoadDefaultWorkspaceLater() { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true); + editor.commit(); + } + + private boolean convertDatabase(SQLiteDatabase db) { + if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); + boolean converted = false; + + final Uri uri = Uri.parse("content://" + Settings.AUTHORITY + + "/old_favorites?notify=true"); + final ContentResolver resolver = mContext.getContentResolver(); + Cursor cursor = null; + + try { + cursor = resolver.query(uri, null, null, null, null); + } catch (Exception e) { + // Ignore + } + + // We already have a favorites database in the old provider + if (cursor != null && cursor.getCount() > 0) { + try { + converted = copyFromCursor(db, cursor) > 0; + } finally { + cursor.close(); + } + + if (converted) { + resolver.delete(uri, null, null); + } + } + + if (converted) { + // Convert widgets from this import into widgets + if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); + convertWidgets(db); + } + + return converted; + } + + private int copyFromCursor(SQLiteDatabase db, Cursor c) { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); + final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); + final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); + final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + 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 uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); + final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); + + ContentValues[] rows = new ContentValues[c.getCount()]; + int i = 0; + while (c.moveToNext()) { + ContentValues values = new ContentValues(c.getColumnCount()); + values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); + values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); + values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); + values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); + values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); + values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); + values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); + values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); + values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); + values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); + values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); + values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); + values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); + values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); + rows[i++] = values; + } + + db.beginTransaction(); + int total = 0; + try { + int numValues = rows.length; + for (i = 0; i < numValues; i++) { + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { + return 0; + } else { + total++; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return total; + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (LOGD) Log.d(TAG, "onUpgrade triggered"); + + int version = oldVersion; + if (version < 3) { + // upgrade 1,2 -> 3 added appWidgetId column + db.beginTransaction(); + try { + // Insert new column for holding appWidgetIds + db.execSQL("ALTER TABLE favorites " + + "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); + db.setTransactionSuccessful(); + version = 3; + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + // Convert existing widgets only if table upgrade was successful + if (version == 3) { + convertWidgets(db); + } + } + + if (version < 4) { + version = 4; + } + + // Where's version 5? + // - Donut and sholes on 2.0 shipped with version 4 of launcher1. + // - Passion shipped on 2.1 with version 6 of launcher2 + // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 + // but version 5 on there was the updateContactsShortcuts change + // which was version 6 in launcher 2 (first shipped on passion 2.1r1). + // The updateContactsShortcuts change is idempotent, so running it twice + // is okay so we'll do that when upgrading the devices that shipped with it. + if (version < 6) { + // We went from 3 to 5 screens. Move everything 1 to the right + db.beginTransaction(); + try { + db.execSQL("UPDATE favorites SET screen=(screen + 1);"); + db.setTransactionSuccessful(); + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + // We added the fast track. + if (updateContactsShortcuts(db)) { + version = 6; + } + } + + if (version < 7) { + // Version 7 gets rid of the special search widget. + convertWidgets(db); + version = 7; + } + + if (version < 8) { + // Version 8 (froyo) has the icons all normalized. This should + // already be the case in practice, but we now rely on it and don't + // resample the images each time. + normalizeIcons(db); + version = 8; + } + + if (version < 9) { + // The max id is not yet set at this point (onUpgrade is triggered in the ctor + // before it gets a change to get set, so we need to read it here when we use it) + if (mMaxId == -1) { + mMaxId = initializeMaxId(db); + } + + // Add default hotseat icons + loadFavorites(db, R.xml.update_workspace); + version = 9; + } + + // We bumped the version three time during JB, once to update the launch flags, once to + // update the override for the default launch animation and once to set the mimetype + // to improve startup performance + if (version < 12) { + // Contact shortcuts need a different set of flags to be launched now + // The updateContactsShortcuts change is idempotent, so we can keep using it like + // back in the Donut days + updateContactsShortcuts(db); + version = 12; + } + + if (version != DATABASE_VERSION) { + Log.w(TAG, "Destroying all old data."); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); + onCreate(db); + } + } + + private boolean updateContactsShortcuts(SQLiteDatabase db) { + final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, + new int[] { Favorites.ITEM_TYPE_SHORTCUT }); + + Cursor c = null; + final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; + db.beginTransaction(); + try { + // Select and iterate through each matching widget + c = db.query(TABLE_FAVORITES, + new String[] { Favorites._ID, Favorites.INTENT }, + selectWhere, null, null, null, null); + if (c == null) return false; + + if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); + + final int idIndex = c.getColumnIndex(Favorites._ID); + final int intentIndex = c.getColumnIndex(Favorites.INTENT); + + while (c.moveToNext()) { + long favoriteId = c.getLong(idIndex); + final String intentUri = c.getString(intentIndex); + if (intentUri != null) { + try { + final Intent intent = Intent.parseUri(intentUri, 0); + android.util.Log.d("Home", intent.toString()); + final Uri uri = intent.getData(); + if (uri != null) { + final String data = uri.toString(); + if ((Intent.ACTION_VIEW.equals(intent.getAction()) || + actionQuickContact.equals(intent.getAction())) && + (data.startsWith("content://contacts/people/") || + data.startsWith("content://com.android.contacts/" + + "contacts/lookup/"))) { + + final Intent newIntent = new Intent(actionQuickContact); + // When starting from the launcher, start in a new, cleared task + // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we + // clear the whole thing preemptively here since + // QuickContactActivity will finish itself when launching other + // detail activities. + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK); + newIntent.putExtra( + Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); + newIntent.setData(uri); + // Determine the type and also put that in the shortcut + // (that can speed up launch a bit) + newIntent.setDataAndType(uri, newIntent.resolveType(mContext)); + + final ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.INTENT, + newIntent.toUri(0)); + + String updateWhere = Favorites._ID + "=" + favoriteId; + db.update(TABLE_FAVORITES, values, updateWhere, null); + } + } + } catch (RuntimeException ex) { + Log.e(TAG, "Problem upgrading shortcut", ex); + } catch (URISyntaxException e) { + Log.e(TAG, "Problem upgrading shortcut", e); + } + } + } + + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(TAG, "Problem while upgrading contacts", ex); + return false; + } finally { + db.endTransaction(); + if (c != null) { + c.close(); + } + } + + return true; + } + + private void normalizeIcons(SQLiteDatabase db) { + Log.d(TAG, "normalizing icons"); + + db.beginTransaction(); + Cursor c = null; + SQLiteStatement update = null; + try { + boolean logged = false; + update = db.compileStatement("UPDATE favorites " + + "SET icon=? WHERE _id=?"); + + c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + + Favorites.ICON_TYPE_BITMAP, null); + + final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); + final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); + + while (c.moveToNext()) { + long id = c.getLong(idIndex); + byte[] data = c.getBlob(iconIndex); + try { + Bitmap bitmap = Utilities.resampleIconBitmap( + BitmapFactory.decodeByteArray(data, 0, data.length), + mContext); + if (bitmap != null) { + update.bindLong(1, id); + data = ItemInfo.flattenBitmap(bitmap); + if (data != null) { + update.bindBlob(2, data); + update.execute(); + } + bitmap.recycle(); + } + } catch (Exception e) { + if (!logged) { + Log.e(TAG, "Failed normalizing icon " + id, e); + } else { + Log.e(TAG, "Also failed normalizing icon " + id); + } + logged = true; + } + } + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); + } finally { + db.endTransaction(); + if (update != null) { + update.close(); + } + if (c != null) { + c.close(); + } + } + } + + // Generates a new ID to use for an object in your database. This method should be only + // called from the main UI thread. As an exception, we do call it when we call the + // constructor from the worker thread; however, this doesn't extend until after the + // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp + // after that point + public long generateNewId() { + if (mMaxId < 0) { + throw new RuntimeException("Error: max id was not initialized"); + } + mMaxId += 1; + return mMaxId; + } + + private long initializeMaxId(SQLiteDatabase db) { + Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); + + // get the result + final int maxIdIndex = 0; + long id = -1; + if (c != null && c.moveToNext()) { + id = c.getLong(maxIdIndex); + } + if (c != null) { + c.close(); + } + + if (id == -1) { + throw new RuntimeException("Error: could not query max id"); + } + + return id; + } + + /** + * Upgrade existing clock and photo frame widgets into their new widget + * equivalents. + */ + private void convertWidgets(SQLiteDatabase db) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + final int[] bindSources = new int[] { + Favorites.ITEM_TYPE_WIDGET_CLOCK, + Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, + Favorites.ITEM_TYPE_WIDGET_SEARCH, + }; + + final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); + + Cursor c = null; + + db.beginTransaction(); + try { + // Select and iterate through each matching widget + c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, + selectWhere, null, null, null, null); + + if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); + + final ContentValues values = new ContentValues(); + while (c != null && c.moveToNext()) { + long favoriteId = c.getLong(0); + int favoriteType = c.getInt(1); + + // Allocate and update database with new appWidgetId + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + if (LOGD) { + Log.d(TAG, "allocated appWidgetId=" + appWidgetId + + " for favoriteId=" + favoriteId); + } + values.clear(); + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); + values.put(Favorites.APPWIDGET_ID, appWidgetId); + + // Original widgets might not have valid spans when upgrading + if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { + values.put(LauncherSettings.Favorites.SPANX, 4); + values.put(LauncherSettings.Favorites.SPANY, 1); + } else { + values.put(LauncherSettings.Favorites.SPANX, 2); + values.put(LauncherSettings.Favorites.SPANY, 2); + } + + String updateWhere = Favorites._ID + "=" + favoriteId; + db.update(TABLE_FAVORITES, values, updateWhere, null); + + if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { + // TODO: check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, + new ComponentName("com.android.alarmclock", + "com.android.alarmclock.AnalogAppWidgetProvider")); + } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { + // TODO: check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, + new ComponentName("com.android.camera", + "com.android.camera.PhotoAppWidgetProvider")); + } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { + // TODO: check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, + getSearchWidgetProvider()); + } + } catch (RuntimeException ex) { + Log.e(TAG, "Problem allocating appWidgetId", ex); + } + } + + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); + } finally { + db.endTransaction(); + if (c != null) { + c.close(); + } + } + } + + private static final void beginDocument(XmlPullParser parser, String firstElementName) + throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + + if (type != parser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + /** + * Loads the default set of favorite packages from an xml file. + * + * @param db The database to write the values into + * @param filterContainerId The specific container id of items to load + */ + private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { + Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + ContentValues values = new ContentValues(); + + PackageManager packageManager = mContext.getPackageManager(); + int allAppsButtonRank = + mContext.getResources().getInteger(R.integer.hotseat_all_apps_index); + int i = 0; + try { + XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId); + AttributeSet attrs = Xml.asAttributeSet(parser); + beginDocument(parser, TAG_FAVORITES); + + final int depth = parser.getDepth(); + + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + boolean added = false; + final String name = parser.getName(); + + TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); + + long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + if (a.hasValue(R.styleable.Favorite_container)) { + container = Long.valueOf(a.getString(R.styleable.Favorite_container)); + } + + String screen = a.getString(R.styleable.Favorite_screen); + String x = a.getString(R.styleable.Favorite_x); + String y = a.getString(R.styleable.Favorite_y); + + // If we are adding to the hotseat, the screen is used as the position in the + // hotseat. This screen can't be at position 0 because AllApps is in the + // zeroth position. + if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + && Integer.valueOf(screen) == allAppsButtonRank) { + throw new RuntimeException("Invalid screen position for hotseat item"); + } + + values.clear(); + values.put(LauncherSettings.Favorites.CONTAINER, container); + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, x); + values.put(LauncherSettings.Favorites.CELLY, y); + + if (TAG_FAVORITE.equals(name)) { + long id = addAppShortcut(db, values, a, packageManager, intent); + added = id >= 0; + } else if (TAG_SEARCH.equals(name)) { + added = addSearchWidget(db, values); + } else if (TAG_CLOCK.equals(name)) { + added = addClockWidget(db, values); + } else if (TAG_APPWIDGET.equals(name)) { + added = addAppWidget(parser, attrs, type, db, values, a, packageManager); + } else if (TAG_SHORTCUT.equals(name)) { + long id = addUriShortcut(db, values, a); + added = id >= 0; + } else if (TAG_FOLDER.equals(name)) { + String title; + int titleResId = a.getResourceId(R.styleable.Favorite_title, -1); + if (titleResId != -1) { + title = mContext.getResources().getString(titleResId); + } else { + title = mContext.getResources().getString(R.string.folder_name); + } + values.put(LauncherSettings.Favorites.TITLE, title); + long folderId = addFolder(db, values); + added = folderId >= 0; + + ArrayList folderItems = new ArrayList(); + + int folderDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > folderDepth) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final String folder_item_name = parser.getName(); + + TypedArray ar = mContext.obtainStyledAttributes(attrs, + R.styleable.Favorite); + values.clear(); + values.put(LauncherSettings.Favorites.CONTAINER, folderId); + + if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) { + long id = + addAppShortcut(db, values, ar, packageManager, intent); + if (id >= 0) { + folderItems.add(id); + } + } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) { + long id = addUriShortcut(db, values, ar); + if (id >= 0) { + folderItems.add(id); + } + } else { + throw new RuntimeException("Folders can " + + "contain only shortcuts"); + } + ar.recycle(); + } + // We can only have folders with >= 2 items, so we need to remove the + // folder and clean up if less than 2 items were included, or some + // failed to add, and less than 2 were actually added + if (folderItems.size() < 2 && folderId >= 0) { + // We just delete the folder and any items that made it + deleteId(db, folderId); + if (folderItems.size() > 0) { + deleteId(db, folderItems.get(0)); + } + added = false; + } + } + if (added) i++; + a.recycle(); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Got exception parsing favorites.", e); + } catch (IOException e) { + Log.w(TAG, "Got exception parsing favorites.", e); + } catch (RuntimeException e) { + Log.w(TAG, "Got exception parsing favorites.", e); + } + + return i; + } + + private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, + PackageManager packageManager, Intent intent) { + long id = -1; + ActivityInfo info; + String packageName = a.getString(R.styleable.Favorite_packageName); + String className = a.getString(R.styleable.Favorite_className); + try { + ComponentName cn; + try { + cn = new ComponentName(packageName, className); + info = packageManager.getActivityInfo(cn, 0); + } catch (PackageManager.NameNotFoundException nnfe) { + String[] packages = packageManager.currentToCanonicalPackageNames( + new String[] { packageName }); + cn = new ComponentName(packages[0], className); + info = packageManager.getActivityInfo(cn, 0); + } + id = generateNewId(); + intent.setComponent(cn); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + values.put(Favorites.INTENT, intent.toUri(0)); + values.put(Favorites.TITLE, info.loadLabel(packageManager).toString()); + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); + values.put(Favorites.SPANX, 1); + values.put(Favorites.SPANY, 1); + values.put(Favorites._ID, generateNewId()); + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { + return -1; + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to add favorite: " + packageName + + "/" + className, e); + } + return id; + } + + private long addFolder(SQLiteDatabase db, ContentValues values) { + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); + values.put(Favorites.SPANX, 1); + values.put(Favorites.SPANY, 1); + long id = generateNewId(); + values.put(Favorites._ID, id); + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) { + return -1; + } else { + return id; + } + } + + private ComponentName getSearchWidgetProvider() { + SearchManager searchManager = + (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + ComponentName searchComponent = searchManager.getGlobalSearchActivity(); + if (searchComponent == null) return null; + return getProviderInPackage(searchComponent.getPackageName()); + } + + /** + * Gets an appwidget provider from the given package. If the package contains more than + * one appwidget provider, an arbitrary one is returned. + */ + private ComponentName getProviderInPackage(String packageName) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + List providers = appWidgetManager.getInstalledProviders(); + if (providers == null) return null; + final int providerCount = providers.size(); + for (int i = 0; i < providerCount; i++) { + ComponentName provider = providers.get(i).provider; + if (provider != null && provider.getPackageName().equals(packageName)) { + return provider; + } + } + return null; + } + + private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) { + ComponentName cn = getSearchWidgetProvider(); + return addAppWidget(db, values, cn, 4, 1, null); + } + + private boolean addClockWidget(SQLiteDatabase db, ContentValues values) { + ComponentName cn = new ComponentName("com.android.alarmclock", + "com.android.alarmclock.AnalogAppWidgetProvider"); + return addAppWidget(db, values, cn, 2, 2, null); + } + + private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type, + SQLiteDatabase db, ContentValues values, TypedArray a, + PackageManager packageManager) throws XmlPullParserException, IOException { + + String packageName = a.getString(R.styleable.Favorite_packageName); + String className = a.getString(R.styleable.Favorite_className); + + if (packageName == null || className == null) { + return false; + } + + boolean hasPackage = true; + ComponentName cn = new ComponentName(packageName, className); + try { + packageManager.getReceiverInfo(cn, 0); + } catch (Exception e) { + String[] packages = packageManager.currentToCanonicalPackageNames( + new String[] { packageName }); + cn = new ComponentName(packages[0], className); + try { + packageManager.getReceiverInfo(cn, 0); + } catch (Exception e1) { + hasPackage = false; + } + } + + if (hasPackage) { + int spanX = a.getInt(R.styleable.Favorite_spanX, 0); + int spanY = a.getInt(R.styleable.Favorite_spanY, 0); + + // Read the extras + Bundle extras = new Bundle(); + int widgetDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > widgetDepth) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra); + if (TAG_EXTRA.equals(parser.getName())) { + String key = ar.getString(R.styleable.Extra_key); + String value = ar.getString(R.styleable.Extra_value); + if (key != null && value != null) { + extras.putString(key, value); + } else { + throw new RuntimeException("Widget extras must have a key and value"); + } + } else { + throw new RuntimeException("Widgets can contain only extras"); + } + ar.recycle(); + } + + return addAppWidget(db, values, cn, spanX, spanY, extras); + } + + return false; + } + + private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, + int spanX, int spanY, Bundle extras) { + boolean allocatedAppWidgets = false; + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); + values.put(Favorites.SPANX, spanX); + values.put(Favorites.SPANY, spanY); + values.put(Favorites.APPWIDGET_ID, appWidgetId); + values.put(Favorites._ID, generateNewId()); + dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); + + allocatedAppWidgets = true; + + // TODO: need to check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn); + + // Send a broadcast to configure the widget + if (extras != null && !extras.isEmpty()) { + Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); + intent.setComponent(cn); + intent.putExtras(extras); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + mContext.sendBroadcast(intent); + } + } catch (RuntimeException ex) { + Log.e(TAG, "Problem allocating appWidgetId", ex); + } + + return allocatedAppWidgets; + } + + private long addUriShortcut(SQLiteDatabase db, ContentValues values, + TypedArray a) { + Resources r = mContext.getResources(); + + final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0); + final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0); + + Intent intent; + String uri = null; + try { + uri = a.getString(R.styleable.Favorite_uri); + intent = Intent.parseUri(uri, 0); + } catch (URISyntaxException e) { + Log.w(TAG, "Shortcut has malformed uri: " + uri); + return -1; // Oh well + } + + if (iconResId == 0 || titleResId == 0) { + Log.w(TAG, "Shortcut is missing title or icon resource ID"); + return -1; + } + + long id = generateNewId(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + values.put(Favorites.INTENT, intent.toUri(0)); + values.put(Favorites.TITLE, r.getString(titleResId)); + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT); + values.put(Favorites.SPANX, 1); + values.put(Favorites.SPANY, 1); + values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE); + values.put(Favorites.ICON_PACKAGE, mContext.getPackageName()); + values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId)); + values.put(Favorites._ID, id); + + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { + return -1; + } + return id; + } + } + + /** + * Build a query string that will match any row where the column matches + * anything in the values list. + */ + static String buildOrWhereString(String column, int[] values) { + StringBuilder selectWhere = new StringBuilder(); + for (int i = values.length - 1; i >= 0; i--) { + selectWhere.append(column).append("=").append(values[i]); + if (i > 0) { + selectWhere.append(" OR "); + } + } + return selectWhere.toString(); + } + + static class SqlArguments { + public final String table; + public final String where; + public final String[] args; + + SqlArguments(Uri url, String where, String[] args) { + if (url.getPathSegments().size() == 1) { + this.table = url.getPathSegments().get(0); + this.where = where; + this.args = args; + } else if (url.getPathSegments().size() != 2) { + throw new IllegalArgumentException("Invalid URI: " + url); + } else if (!TextUtils.isEmpty(where)) { + throw new UnsupportedOperationException("WHERE clause not supported: " + url); + } else { + this.table = url.getPathSegments().get(0); + this.where = "_id=" + ContentUris.parseId(url); + this.args = null; + } + } + + SqlArguments(Uri url) { + if (url.getPathSegments().size() == 1) { + table = url.getPathSegments().get(0); + where = null; + args = null; + } else { + throw new IllegalArgumentException("Invalid URI: " + url); + } + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherSettings.java b/src/com/cyanogenmod/trebuchet/LauncherSettings.java new file mode 100644 index 000000000..361dd69a0 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherSettings.java @@ -0,0 +1,236 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * Settings related utilities. + */ +class LauncherSettings { + static interface BaseLauncherColumns extends BaseColumns { + /** + * Descriptive name of the gesture that can be displayed to the user. + *

Type: TEXT

+ */ + static final String TITLE = "title"; + + /** + * The Intent URL of the gesture, describing what it points to. This + * value is given to {@link android.content.Intent#parseUri(String, int)} to create + * an Intent that can be launched. + *

Type: TEXT

+ */ + static final String INTENT = "intent"; + + /** + * The type of the gesture + * + *

Type: INTEGER

+ */ + static final String ITEM_TYPE = "itemType"; + + /** + * The gesture is an application + */ + static final int ITEM_TYPE_APPLICATION = 0; + + /** + * The gesture is an application created shortcut + */ + static final int ITEM_TYPE_SHORTCUT = 1; + + /** + * The icon type. + *

Type: INTEGER

+ */ + static final String ICON_TYPE = "iconType"; + + /** + * The icon is a resource identified by a package name and an integer id. + */ + static final int ICON_TYPE_RESOURCE = 0; + + /** + * The icon is a bitmap. + */ + static final int ICON_TYPE_BITMAP = 1; + + /** + * The icon package name, if icon type is ICON_TYPE_RESOURCE. + *

Type: TEXT

+ */ + static final String ICON_PACKAGE = "iconPackage"; + + /** + * The icon resource id, if icon type is ICON_TYPE_RESOURCE. + *

Type: TEXT

+ */ + static final String ICON_RESOURCE = "iconResource"; + + /** + * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP. + *

Type: BLOB

+ */ + static final String ICON = "icon"; + } + + /** + * Favorites. + */ + static final class Favorites implements BaseLauncherColumns { + /** + * The content:// style URL for this table + */ + static final Uri CONTENT_URI = Uri.parse("content://" + + LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + + "?" + LauncherProvider.PARAMETER_NOTIFY + "=true"); + + /** + * The content:// style URL for this table. When this Uri is used, no notification is + * sent if the content changes. + */ + static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" + + LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + + "?" + LauncherProvider.PARAMETER_NOTIFY + "=false"); + + /** + * The content:// style URL for a given row, identified by its id. + * + * @param id The row id. + * @param notify True to send a notification is the content changes. + * + * @return The unique content URL for the specified row. + */ + static Uri getContentUri(long id, boolean notify) { + return Uri.parse("content://" + LauncherProvider.AUTHORITY + + "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" + + LauncherProvider.PARAMETER_NOTIFY + "=" + notify); + } + + /** + * The container holding the favorite + *

Type: INTEGER

+ */ + static final String CONTAINER = "container"; + + /** + * The icon is a resource identified by a package name and an integer id. + */ + static final int CONTAINER_DESKTOP = -100; + static final int CONTAINER_HOTSEAT = -101; + + /** + * The screen holding the favorite (if container is CONTAINER_DESKTOP) + *

Type: INTEGER

+ */ + static final String SCREEN = "screen"; + + /** + * The X coordinate of the cell holding the favorite + * (if container is CONTAINER_HOTSEAT or CONTAINER_HOTSEAT) + *

Type: INTEGER

+ */ + static final String CELLX = "cellX"; + + /** + * The Y coordinate of the cell holding the favorite + * (if container is CONTAINER_DESKTOP) + *

Type: INTEGER

+ */ + static final String CELLY = "cellY"; + + /** + * The X span of the cell holding the favorite + *

Type: INTEGER

+ */ + static final String SPANX = "spanX"; + + /** + * The Y span of the cell holding the favorite + *

Type: INTEGER

+ */ + static final String SPANY = "spanY"; + + /** + * The favorite is a user created folder + */ + static final int ITEM_TYPE_FOLDER = 2; + + /** + * The favorite is a live folder + * + * Note: live folders can no longer be added to Launcher, and any live folders which + * exist within the launcher database will be ignored when loading. That said, these + * entries in the database may still exist, and are not automatically stripped. + */ + static final int ITEM_TYPE_LIVE_FOLDER = 3; + + /** + * The favorite is a widget + */ + static final int ITEM_TYPE_APPWIDGET = 4; + + /** + * The favorite is a clock + */ + static final int ITEM_TYPE_WIDGET_CLOCK = 1000; + + /** + * The favorite is a search widget + */ + static final int ITEM_TYPE_WIDGET_SEARCH = 1001; + + /** + * The favorite is a photo frame + */ + static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002; + + /** + * The appWidgetId of the widget + * + *

Type: INTEGER

+ */ + static final String APPWIDGET_ID = "appWidgetId"; + + /** + * Indicates whether this favorite is an application-created shortcut or not. + * If the value is 0, the favorite is not an application-created shortcut, if the + * value is 1, it is an application-created shortcut. + *

Type: INTEGER

+ */ + @Deprecated + static final String IS_SHORTCUT = "isShortcut"; + + /** + * The URI associated with the favorite. It is used, for instance, by + * live folders to find the content provider. + *

Type: TEXT

+ */ + static final String URI = "uri"; + + /** + * The display mode if the item is a live folder. + *

Type: INTEGER

+ * + * @see android.provider.LiveFolders#DISPLAY_MODE_GRID + * @see android.provider.LiveFolders#DISPLAY_MODE_LIST + */ + static final String DISPLAY_MODE = "displayMode"; + } +} diff --git a/src/com/cyanogenmod/trebuchet/LauncherViewPropertyAnimator.java b/src/com/cyanogenmod/trebuchet/LauncherViewPropertyAnimator.java new file mode 100644 index 000000000..d02cb25c6 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/LauncherViewPropertyAnimator.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2012 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.TimeInterpolator; +import android.view.ViewPropertyAnimator; +import android.view.View; + +import java.util.ArrayList; +import java.util.EnumSet; + +public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener { + enum Properties { + TRANSLATION_X, + TRANSLATION_Y, + SCALE_X, + SCALE_Y, + ROTATION_Y, + ALPHA, + START_DELAY, + DURATION, + INTERPOLATOR + } + EnumSet mPropertiesToSet = EnumSet.noneOf(Properties.class); + ViewPropertyAnimator mViewPropertyAnimator; + View mTarget; + + float mTranslationX; + float mTranslationY; + float mScaleX; + float mScaleY; + float mRotationY; + float mAlpha; + long mStartDelay; + long mDuration; + TimeInterpolator mInterpolator; + ArrayList mListeners; + boolean mRunning = false; + + public LauncherViewPropertyAnimator(View target) { + mTarget = target; + mListeners = new ArrayList(); + } + + @Override + public void addListener(Animator.AnimatorListener listener) { + mListeners.add(listener); + } + + @Override + public void cancel() { + if (mViewPropertyAnimator != null) { + mViewPropertyAnimator.cancel(); + } + } + + @Override + public Animator clone() { + throw new RuntimeException("Not implemented"); + } + + @Override + public void end() { + throw new RuntimeException("Not implemented"); + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public ArrayList getListeners() { + return mListeners; + } + + @Override + public long getStartDelay() { + return mStartDelay; + } + + @Override + public void onAnimationCancel(Animator animation) { + for (int i = 0; i < mListeners.size(); i++) { + Animator.AnimatorListener listener = mListeners.get(i); + listener.onAnimationCancel(this); + } + mRunning = false; + } + + @Override + public void onAnimationEnd(Animator animation) { + for (int i = 0; i < mListeners.size(); i++) { + Animator.AnimatorListener listener = mListeners.get(i); + listener.onAnimationEnd(this); + } + mRunning = false; + } + + @Override + public void onAnimationRepeat(Animator animation) { + for (int i = 0; i < mListeners.size(); i++) { + Animator.AnimatorListener listener = mListeners.get(i); + listener.onAnimationRepeat(this); + } + } + + @Override + public void onAnimationStart(Animator animation) { + for (int i = 0; i < mListeners.size(); i++) { + Animator.AnimatorListener listener = mListeners.get(i); + listener.onAnimationStart(this); + } + mRunning = true; + } + + @Override + public boolean isRunning() { + return mRunning; + } + + @Override + public boolean isStarted() { + return mViewPropertyAnimator != null; + } + + @Override + public void removeAllListeners() { + mListeners.clear(); + } + + @Override + public void removeListener(Animator.AnimatorListener listener) { + mListeners.remove(listener); + } + + @Override + public Animator setDuration(long duration) { + mPropertiesToSet.add(Properties.DURATION); + mDuration = duration; + return this; + } + + @Override + public void setInterpolator(TimeInterpolator value) { + mPropertiesToSet.add(Properties.INTERPOLATOR); + mInterpolator = value; + } + + @Override + public void setStartDelay(long startDelay) { + mPropertiesToSet.add(Properties.START_DELAY); + mStartDelay = startDelay; + } + + @Override + public void setTarget(Object target) { + throw new RuntimeException("Not implemented"); + } + + @Override + public void setupEndValues() { + + } + + @Override + public void setupStartValues() { + } + + @Override + public void start() { + mViewPropertyAnimator = mTarget.animate(); + if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) { + mViewPropertyAnimator.translationX(mTranslationX); + } + if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) { + mViewPropertyAnimator.translationY(mTranslationY); + } + if (mPropertiesToSet.contains(Properties.SCALE_X)) { + mViewPropertyAnimator.scaleX(mScaleX); + } + if (mPropertiesToSet.contains(Properties.ROTATION_Y)) { + mViewPropertyAnimator.rotationY(mRotationY); + } + if (mPropertiesToSet.contains(Properties.SCALE_Y)) { + mViewPropertyAnimator.scaleY(mScaleY); + } + if (mPropertiesToSet.contains(Properties.ALPHA)) { + mViewPropertyAnimator.alpha(mAlpha); + } + if (mPropertiesToSet.contains(Properties.START_DELAY)) { + mViewPropertyAnimator.setStartDelay(mStartDelay); + } + if (mPropertiesToSet.contains(Properties.DURATION)) { + mViewPropertyAnimator.setDuration(mDuration); + } + if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) { + mViewPropertyAnimator.setInterpolator(mInterpolator); + } + mViewPropertyAnimator.setListener(this); + mViewPropertyAnimator.start(); + } + + public LauncherViewPropertyAnimator translationX(float value) { + mPropertiesToSet.add(Properties.TRANSLATION_X); + mTranslationX = value; + return this; + } + + public LauncherViewPropertyAnimator translationY(float value) { + mPropertiesToSet.add(Properties.TRANSLATION_Y); + mTranslationY = value; + return this; + } + + public LauncherViewPropertyAnimator scaleX(float value) { + mPropertiesToSet.add(Properties.SCALE_X); + mScaleX = value; + return this; + } + + public LauncherViewPropertyAnimator scaleY(float value) { + mPropertiesToSet.add(Properties.SCALE_Y); + mScaleY = value; + return this; + } + + public LauncherViewPropertyAnimator rotationY(float value) { + mPropertiesToSet.add(Properties.ROTATION_Y); + mRotationY = value; + return this; + } + + public LauncherViewPropertyAnimator alpha(float value) { + mPropertiesToSet.add(Properties.ALPHA); + mAlpha = value; + return this; + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedView.java b/src/com/cyanogenmod/trebuchet/PagedView.java new file mode 100644 index 000000000..42c9ca9c4 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedView.java @@ -0,0 +1,1913 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyEvent; +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.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import com.cyanogenmod.trebuchet.R; + +import java.util.ArrayList; + +/** + * An abstraction of the original Workspace which supports browsing through a + * sequential list of "pages" + */ +public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { + private static final String TAG = "PagedView"; + private static final boolean DEBUG = false; + 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; + + protected static final int PAGE_SNAP_ANIMATION_DURATION = 550; + protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; + protected static final float NANOTIME_DIV = 1000000000.0f; + + private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; + private static final float OVERSCROLL_DAMP_FACTOR = 0.14f; + + private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; + // The page is moved more than halfway, automatically move to the next page on touch up. + private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + + // The following constants need to be scaled based on density. The scaled versions will be + // assigned to the corresponding member variables below. + private static final int FLING_THRESHOLD_VELOCITY = 500; + private static final int MIN_SNAP_VELOCITY = 1500; + private static final int MIN_FLING_VELOCITY = 250; + + static final int AUTOMATIC_PAGE_SPACING = -1; + + protected int mFlingThresholdVelocity; + protected int mMinFlingVelocity; + protected int mMinSnapVelocity; + + protected float mDensity; + protected float mSmoothingTime; + protected float mTouchX; + + protected boolean mFirstLayout = true; + + protected int mCurrentPage; + protected int mNextPage = INVALID_PAGE; + protected int mMaxScrollX; + protected Scroller mScroller; + private VelocityTracker mVelocityTracker; + + private float mDownMotionX; + protected float mLastMotionX; + protected float mLastMotionXRemainder; + protected float mLastMotionY; + protected float mTotalMotionX; + private int mLastScreenCenter = -1; + private int[] mChildOffsets; + private int[] mChildRelativeOffsets; + private int[] mChildOffsetsWithLayoutScale; + + 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 boolean mForceScreenScrolled = false; + + protected OnLongClickListener mLongClickListener; + + protected boolean mAllowLongPress = true; + + protected int mTouchSlop; + private int mPagingTouchSlop; + private int mMaximumVelocity; + private int mMinimumWidth; + protected int mPageSpacing; + protected int mPageLayoutPaddingTop; + protected int mPageLayoutPaddingBottom; + protected int mPageLayoutPaddingLeft; + protected int mPageLayoutPaddingRight; + protected int mPageLayoutWidthGap; + protected int mPageLayoutHeightGap; + protected int mCellCountX = 0; + protected int mCellCountY = 0; + protected boolean mCenterPagesVertically; + protected boolean mAllowOverScroll = true; + protected int mUnboundedScrollX; + protected int[] mTempVisiblePagesRange = new int[2]; + protected boolean mForceDrawAllChildrenNextFrame; + + // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise + // it is equal to the scaled overscroll position. We use a separate value so as to prevent + // the screens from continuing to translate beyond the normal bounds. + protected int mOverScrollX; + + // parameter that adjusts the layout to be optimized for pages with that scale factor + protected float mLayoutScale = 1.0f; + + protected static final int INVALID_POINTER = -1; + + protected int mActivePointerId = INVALID_POINTER; + + private PageSwitchListener mPageSwitchListener; + + protected ArrayList mDirtyPageContent; + + // 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 scrollX itself in its computeScroll method + // (SmoothPagedView does this) + protected boolean mDeferScrollUpdate = false; + + protected boolean mIsPageMoving = false; + + // All syncs and layout passes are deferred until data is ready. + protected boolean mIsDataReady = false; + + // Scrolling indicator + private ValueAnimator mScrollIndicatorAnimator; + private View mScrollIndicator; + private int mScrollIndicatorPaddingLeft; + private int mScrollIndicatorPaddingRight; + private boolean mHasScrollIndicator = true; + private boolean mShouldShowScrollIndicator = false; + private boolean mShouldShowScrollIndicatorImmediately = false; + protected static final int sScrollIndicatorFadeInDuration = 150; + protected static final int sScrollIndicatorFadeOutDuration = 650; + protected static final int sScrollIndicatorFlashDuration = 650; + + // If set, will defer loading associated pages until the scrolling settles + private boolean mDeferLoadAssociatedPagesUntilScrollCompletes; + + 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); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.PagedView, defStyle, 0); + setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0)); + mPageLayoutPaddingTop = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingTop, 0); + mPageLayoutPaddingBottom = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingBottom, 0); + mPageLayoutPaddingLeft = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingLeft, 0); + mPageLayoutPaddingRight = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingRight, 0); + mPageLayoutWidthGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutWidthGap, 0); + mPageLayoutHeightGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutHeightGap, 0); + mScrollIndicatorPaddingLeft = + a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0); + mScrollIndicatorPaddingRight = + a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0); + a.recycle(); + + setHapticFeedbackEnabled(false); + init(); + } + + /** + * Initializes various states for this workspace. + */ + protected void init() { + mDirtyPageContent = new ArrayList(); + mDirtyPageContent.ensureCapacity(32); + mScroller = new Scroller(getContext(), new ScrollInterpolator()); + mCurrentPage = 0; + mCenterPagesVertically = true; + + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mDensity = getResources().getDisplayMetrics().density; + + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); + mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); + mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); + setOnHierarchyChangeListener(this); + } + + public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { + mPageSwitchListener = pageSwitchListener; + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + /** + * Called by subclasses to mark that data is ready, and that we can begin loading and laying + * out pages. + */ + protected void setDataIsReady() { + mIsDataReady = true; + } + protected boolean isDataReady() { + return mIsDataReady; + } + + /** + * Returns the index of the currently displayed page. + * + * @return The index of the currently displayed page. + */ + int getCurrentPage() { + return mCurrentPage; + } + int getNextPage() { + return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + } + + int getPageCount() { + return getChildCount(); + } + + View getPageAt(int index) { + return getChildAt(index); + } + + protected int indexToPage(int index) { + return index; + } + + /** + * Updates the scroll of the current page immediately to its final scroll position. We use this + * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of + * the previous tab page. + */ + protected void updateCurrentPageScroll() { + int offset = getChildOffset(mCurrentPage); + int relOffset = getRelativeChildOffset(mCurrentPage); + int newX = offset - relOffset; + scrollTo(newX, 0); + mScroller.setFinalX(newX); + mScroller.forceFinished(true); + } + + /** + * Sets the current page. + */ + void setCurrentPage(int currentPage) { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + // don't introduce any checks like mCurrentPage == currentPage here-- if we change the + // the default + if (getChildCount() == 0) { + return; + } + + + mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); + updateCurrentPageScroll(); + updateScrollingIndicator(); + notifyPageSwitchListener(); + invalidate(); + } + + protected void notifyPageSwitchListener() { + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + protected void pageBeginMoving() { + if (!mIsPageMoving) { + mIsPageMoving = true; + onPageBeginMoving(); + } + } + + protected void pageEndMoving() { + if (mIsPageMoving) { + mIsPageMoving = false; + onPageEndMoving(); + } + } + + protected boolean isPageMoving() { + return mIsPageMoving; + } + + // 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 scrollBy(int x, int y) { + scrollTo(mUnboundedScrollX + x, getScrollY() + y); + } + + @Override + public void scrollTo(int x, int y) { + mUnboundedScrollX = x; + + if (x < 0) { + super.scrollTo(0, y); + if (mAllowOverScroll) { + overScroll(x); + } + } else if (x > mMaxScrollX) { + super.scrollTo(mMaxScrollX, y); + if (mAllowOverScroll) { + overScroll(x - mMaxScrollX); + } + } else { + mOverScrollX = x; + 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()) { + // Don't bother scrolling if the page does not need to be moved + if (getScrollX() != mScroller.getCurrX() + || getScrollY() != mScroller.getCurrY() + || mOverScrollX != mScroller.getCurrX()) { + scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); + } + invalidate(); + return true; + } else if (mNextPage != INVALID_PAGE) { + mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); + mNextPage = INVALID_PAGE; + notifyPageSwitchListener(); + + // Load the associated pages if necessary + if (mDeferLoadAssociatedPagesUntilScrollCompletes) { + loadAssociatedPages(mCurrentPage); + mDeferLoadAssociatedPagesUntilScrollCompletes = false; + } + + // We don't want to trigger a page end moving unless the page has settled + // and the user has stopped scrolling + if (mTouchState == TOUCH_STATE_REST) { + pageEndMoving(); + } + + // Notify the user when the page changes + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent ev = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + ev.getText().add(getCurrentPageDescription()); + sendAccessibilityEventUnchecked(ev); + } + return true; + } + return false; + } + + @Override + public void computeScroll() { + computeScrollHelper(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (!mIsDataReady) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + if (widthMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); + } + + // Return early if we aren't given a proper dimension + if (widthSize <= 0 || heightSize <= 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + /* Allow the height to be set as WRAP_CONTENT. This allows the particular case + * of the All apps view on XLarge displays to not take up more space then it needs. Width + * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect + * each page to have the same width. + */ + int maxChildHeight = 0; + + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int horizontalPadding = getPaddingLeft() + getPaddingRight(); + + + // The children are given the same width and height as the workspace + // unless they were set to WRAP_CONTENT + if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + // disallowing padding in paged view (just pass 0) + final View child = getPageAt(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 - horizontalPadding, childWidthMode); + final int childHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); + if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", " + + child.getMeasuredHeight()); + } + + if (heightMode == MeasureSpec.AT_MOST) { + heightSize = maxChildHeight + verticalPadding; + } + + setMeasuredDimension(widthSize, heightSize); + + // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions. + // We also wait until we set the measured dimensions before flushing the cache as well, to + // ensure that the cache is filled with good values. + invalidateCachedOffsets(); + + if (childCount > 0) { + if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", " + + getChildWidth(0)); + + // Calculate the variable page spacing if necessary + if (mPageSpacing == AUTOMATIC_PAGE_SPACING) { + // The gap between pages in the PagedView should be equal to the gap from the page + // to the edge of the screen (so it is not visible in the current screen). To + // account for unequal padding on each side of the paged view, we take the maximum + // of the left/right gap and use that as the gap between each page. + int offset = getRelativeChildOffset(0); + int spacing = Math.max(offset, widthSize - offset - + getChildAt(0).getMeasuredWidth()); + setPageSpacing(spacing); + } + } + + updateScrollingIndicatorPosition(); + + if (childCount > 0) { + mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); + } else { + mMaxScrollX = 0; + } + } + + protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) { + int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); + int delta = newX - getScrollX(); + + final int pageCount = getChildCount(); + for (int i = 0; i < pageCount; i++) { + View page = (View) getPageAt(i); + page.setX(page.getX() + delta); + } + setCurrentPage(newCurrentPage); + } + + // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a + // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and + // tightens the layout accordingly + public void setLayoutScale(float childrenScale) { + mLayoutScale = childrenScale; + invalidateCachedOffsets(); + + // Now we need to do a re-layout, but preserving absolute X and Y coordinates + int childCount = getChildCount(); + float childrenX[] = new float[childCount]; + float childrenY[] = new float[childCount]; + for (int i = 0; i < childCount; i++) { + final View child = getPageAt(i); + childrenX[i] = child.getX(); + childrenY[i] = child.getY(); + } + // Trigger a full re-layout (never just call onLayout directly!) + int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); + int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); + requestLayout(); + measure(widthSpec, heightSpec); + layout(getLeft(), getTop(), getRight(), getBottom()); + for (int i = 0; i < childCount; i++) { + final View child = getPageAt(i); + child.setX(childrenX[i]); + child.setY(childrenY[i]); + } + + // Also, the page offset has changed (since the pages are now smaller); + // update the page offset, but again preserving absolute X and Y coordinates + scrollToNewPageWithoutMovingPages(mCurrentPage); + } + + public void setPageSpacing(int pageSpacing) { + mPageSpacing = pageSpacing; + invalidateCachedOffsets(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mIsDataReady) { + return; + } + + if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int childCount = getChildCount(); + int childLeft = getRelativeChildOffset(0); + + for (int i = 0; i < childCount; i++) { + final View child = getPageAt(i); + if (child.getVisibility() != View.GONE) { + final int childWidth = getScaledMeasuredWidth(child); + final int childHeight = child.getMeasuredHeight(); + int childTop = getPaddingTop(); + if (mCenterPagesVertically) { + childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; + } + + if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), childTop + childHeight); + childLeft += childWidth + mPageSpacing; + } + } + + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + setHorizontalScrollBarEnabled(false); + updateCurrentPageScroll(); + setHorizontalScrollBarEnabled(true); + mFirstLayout = false; + } + } + + protected void screenScrolled(int screenCenter) { + if (isScrollingIndicatorEnabled()) { + updateScrollingIndicator(); + } + boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; + + if (mFadeInAdjacentScreens && !isInOverscroll) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child != null) { + float scrollProgress = getScrollProgress(screenCenter, child, i); + float alpha = 1 - Math.abs(scrollProgress); + child.setAlpha(alpha); + } + } + invalidate(); + } + } + + @Override + public void onChildViewAdded(View parent, View child) { + // This ensures that when children are added, they get the correct transforms / alphas + // in accordance with any scroll effects. + mForceScreenScrolled = true; + invalidate(); + invalidateCachedOffsets(); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + } + + protected void invalidateCachedOffsets() { + int count = getChildCount(); + if (count == 0) { + mChildOffsets = null; + mChildRelativeOffsets = null; + mChildOffsetsWithLayoutScale = null; + return; + } + + mChildOffsets = new int[count]; + mChildRelativeOffsets = new int[count]; + mChildOffsetsWithLayoutScale = new int[count]; + for (int i = 0; i < count; i++) { + mChildOffsets[i] = -1; + mChildRelativeOffsets[i] = -1; + mChildOffsetsWithLayoutScale[i] = -1; + } + } + + protected int getChildOffset(int index) { + int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ? + mChildOffsets : mChildOffsetsWithLayoutScale; + + if (childOffsets != null && childOffsets[index] != -1) { + return childOffsets[index]; + } else { + if (getChildCount() == 0) + return 0; + + int offset = getRelativeChildOffset(0); + for (int i = 0; i < index; ++i) { + offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing; + } + if (childOffsets != null) { + childOffsets[index] = offset; + } + return offset; + } + } + + protected int getRelativeChildOffset(int index) { + if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) { + return mChildRelativeOffsets[index]; + } else { + final int padding = getPaddingLeft() + getPaddingRight(); + final int offset = getPaddingLeft() + + (getMeasuredWidth() - padding - getChildWidth(index)) / 2; + if (mChildRelativeOffsets != null) { + mChildRelativeOffsets[index] = offset; + } + return offset; + } + } + + protected int getScaledMeasuredWidth(View child) { + // This functions are called enough times that it actually makes a difference in the + // profiler -- so just inline the max() here + final int measuredWidth = child.getMeasuredWidth(); + final int minWidth = mMinimumWidth; + final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth; + return (int) (maxWidth * mLayoutScale + 0.5f); + } + + protected void getVisiblePages(int[] range) { + final int pageCount = getChildCount(); + + if (pageCount > 0) { + final int screenWidth = getMeasuredWidth(); + int leftScreen = 0; + int rightScreen = 0; + View currPage = getPageAt(leftScreen); + while (leftScreen < pageCount - 1 && + currPage.getX() + currPage.getWidth() - + currPage.getPaddingRight() < getScrollX()) { + leftScreen++; + currPage = getPageAt(leftScreen); + } + rightScreen = leftScreen; + currPage = getPageAt(rightScreen + 1); + while (rightScreen < pageCount - 1 && + currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) { + rightScreen++; + currPage = getPageAt(rightScreen + 1); + } + range[0] = leftScreen; + range[1] = rightScreen; + } else { + range[0] = -1; + range[1] = -1; + } + } + + protected boolean shouldDrawChild(View child) { + return child.getAlpha() > 0; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + int halfScreenSize = getMeasuredWidth() / 2; + // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. + // Otherwise it is equal to the scaled overscroll position. + int screenCenter = mOverScrollX + halfScreenSize; + + if (screenCenter != mLastScreenCenter || mForceScreenScrolled) { + // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can + // set it for the next frame + mForceScreenScrolled = false; + screenScrolled(screenCenter); + mLastScreenCenter = screenCenter; + } + + // Find out which screens are visible; as an optimization we only call draw on them + final int pageCount = getChildCount(); + if (pageCount > 0) { + getVisiblePages(mTempVisiblePagesRange); + final int leftScreen = mTempVisiblePagesRange[0]; + final int rightScreen = mTempVisiblePagesRange[1]; + if (leftScreen != -1 && rightScreen != -1) { + final long drawingTime = getDrawingTime(); + // Clip to the bounds + canvas.save(); + canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(), + getScrollY() + getBottom() - getTop()); + + // On certain graphics drivers, if you draw to a off-screen buffer that's not + // used, it can lead to poor performance. We were running into this when + // setChildrenLayersEnabled was called on a CellLayout; that triggered a re-draw + // of that CellLayout's hardware layer, even if that CellLayout wasn't visible. + // As a fix, below we set pages that aren't going to be rendered are to be + // View.INVISIBLE, preventing re-drawing of their hardware layer + for (int i = getChildCount() - 1; i >= 0; i--) { + final View v = getPageAt(i); + if (mForceDrawAllChildrenNextFrame || + (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) { + v.setVisibility(VISIBLE); + drawChild(canvas, v, drawingTime); + } else { + v.setVisibility(INVISIBLE); + } + } + mForceDrawAllChildrenNextFrame = false; + canvas.restore(); + } + } + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + int page = indexToPage(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) { + return 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 views, int direction, int focusableMode) { + if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { + getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode); + } + if (direction == View.FOCUS_LEFT) { + if (mCurrentPage > 0) { + getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode); + } + } else if (direction == View.FOCUS_RIGHT){ + if (mCurrentPage < getPageCount() - 1) { + getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode); + } + } + } + + /** + * 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 = getPageAt(mCurrentPage); + currentPage.cancelLongPress(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + /** + * Return true if a tap at (x, y) should trigger a flip to the previous page. + */ + protected boolean hitsPreviousPage(float x, float y) { + return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); + } + + /** + * Return true if a tap at (x, y) should trigger a flip to the next page. + */ + protected boolean hitsNextPage(float x, float y) { + return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); + } + + @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. + */ + acquireVelocityTrackerAndAddMovement(ev); + + // Skip touch handling if there are no pages to swipe + if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); + + /* + * 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; + mLastMotionXRemainder = 0; + mTotalMotionX = 0; + 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 && mTouchState != TOUCH_STATE_NEXT_PAGE) { + if (getChildCount() > 0) { + if (hitsPreviousPage(x, y)) { + mTouchState = TOUCH_STATE_PREV_PAGE; + } else if (hitsNextPage(x, y)) { + mTouchState = TOUCH_STATE_NEXT_PAGE; + } + } + } + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTouchState = TOUCH_STATE_REST; + mAllowLongPress = false; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + releaseVelocityTracker(); + 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 determineScrollingStart(MotionEvent ev) { + determineScrollingStart(ev, 1.0f); + } + + /* + * 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, float touchSlopScale) { + /* + * Locally do absolute value. mLastMotionX is set to the y value + * of the down event. + */ + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + return; + } + 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 = Math.round(touchSlopScale * mTouchSlop); + boolean xPaged = xDiff > mPagingTouchSlop; + boolean xMoved = xDiff > touchSlop; + boolean yMoved = yDiff > touchSlop; + + if (xMoved || xPaged || yMoved) { + if (mUsePagingTouchSlop ? xPaged : xMoved) { + // Scroll if the user moved far enough along the X axis + mTouchState = TOUCH_STATE_SCROLLING; + mTotalMotionX += Math.abs(mLastMotionX - x); + mLastMotionX = x; + mLastMotionXRemainder = 0; + mTouchX = getScrollX(); + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + pageBeginMoving(); + } + // Either way, cancel any pending longpress + cancelCurrentPageLongPress(); + } + } + + protected void cancelCurrentPageLongPress() { + 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 float getScrollProgress(int screenCenter, View v, int page) { + final int halfScreenSize = getMeasuredWidth() / 2; + + int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; + int delta = screenCenter - (getChildOffset(page) - + getRelativeChildOffset(page) + halfScreenSize); + + float scrollProgress = delta / (totalDistance * 1.0f); + scrollProgress = Math.min(scrollProgress, 1.0f); + scrollProgress = Math.max(scrollProgress, -1.0f); + return scrollProgress; + } + + // This curve determines how the effect of scrolling over the limits of the page dimishes + // as the user pulls further and further from the bounds + private float overScrollInfluenceCurve(float f) { + f -= 1.0f; + return f * f * f + 1.0f; + } + + protected void acceleratedOverScroll(float amount) { + int screenSize = getMeasuredWidth(); + + // We want to reach the max over scroll effect when the user has + // over scrolled half the size of the screen + float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); + + if (f == 0) return; + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + int overScrollAmount = (int) Math.round(f * screenSize); + if (amount < 0) { + mOverScrollX = overScrollAmount; + super.scrollTo(0, getScrollY()); + } else { + mOverScrollX = mMaxScrollX + overScrollAmount; + super.scrollTo(mMaxScrollX, getScrollY()); + } + invalidate(); + } + + protected void dampedOverScroll(float amount) { + int screenSize = getMeasuredWidth(); + + float f = (amount / screenSize); + + if (f == 0) return; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); + if (amount < 0) { + mOverScrollX = overScrollAmount; + super.scrollTo(0, getScrollY()); + } else { + mOverScrollX = mMaxScrollX + overScrollAmount; + super.scrollTo(mMaxScrollX, getScrollY()); + } + invalidate(); + } + + protected void overScroll(float amount) { + dampedOverScroll(amount); + } + + protected float maxOverScroll() { + // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not + // exceed). Used to find out how much extra wallpaper we need for the over scroll effect + float f = 1.0f; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + return OVERSCROLL_DAMP_FACTOR * f; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Skip touch handling if there are no pages to swipe + if (getChildCount() <= 0) return super.onTouchEvent(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(); + mLastMotionXRemainder = 0; + mTotalMotionX = 0; + 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 float deltaX = mLastMotionX + mLastMotionXRemainder - x; + + mTotalMotionX += Math.abs(deltaX); + + // Only scroll and update mLastMotionX if we have moved some discrete amount. We + // keep the remainder because we are actually testing if we've moved from the last + // scrolled position (which is discrete). + if (Math.abs(deltaX) >= 1.0f) { + mTouchX += deltaX; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + if (!mDeferScrollUpdate) { + scrollBy((int) deltaX, 0); + if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); + } else { + invalidate(); + } + mLastMotionX = x; + mLastMotionXRemainder = deltaX - (int) deltaX; + } 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); + final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); + boolean isSignificantMove = Math.abs(deltaX) > pageWidth * + SIGNIFICANT_MOVE_THRESHOLD; + + mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); + + boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && + Math.abs(velocityX) > mFlingThresholdVelocity; + + // In the case that the page is moved far to one direction and then is flung + // in the opposite direction, we use a threshold to determine whether we should + // just return to the starting page, or if we should skip one further. + boolean returnToOriginalPage = false; + if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && + Math.signum(velocityX) != Math.signum(deltaX) && isFling) { + returnToOriginalPage = true; + } + + int finalPage; + // We give flings precedence over large moves, which is why we short-circuit our + // test for a large move if a fling has been registered. That is, a large + // move to the left and fling to the right will register as a fling to the right. + if (((isSignificantMove && deltaX > 0 && !isFling) || + (isFling && velocityX > 0)) && mCurrentPage > 0) { + finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; + snapToPageWithVelocity(finalPage, velocityX); + } else if (((isSignificantMove && deltaX < 0 && !isFling) || + (isFling && velocityX < 0)) && + mCurrentPage < getChildCount() - 1) { + finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; + snapToPageWithVelocity(finalPage, velocityX); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { + // 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) { + // 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(); + } + } else { + onUnhandledTap(ev); + } + 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; + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + switch (event.getAction()) { + case MotionEvent.ACTION_SCROLL: { + // Handle mouse (or ext. device) by shifting the page depending on the scroll + final float vscroll; + final float hscroll; + if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { + vscroll = 0; + hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + } else { + vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); + hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + } + if (hscroll != 0 || vscroll != 0) { + if (hscroll > 0 || vscroll > 0) { + scrollRight(); + } else { + scrollLeft(); + } + return true; + } + } + } + } + return super.onGenericMotionEvent(event); + } + + 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); + mLastMotionXRemainder = 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + protected void onUnhandledTap(MotionEvent ev) {} + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + int page = indexToPage(indexOfChild(child)); + if (page >= 0 && page != getCurrentPage() && !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 + getScaledMeasuredWidth(getPageAt(i))); + if (left <= relativeOffset && relativeOffset <= right) { + return i; + } + } + return -1; + } + + protected int getChildWidth(int index) { + // This functions are called enough times that it actually makes a difference in the + // profiler -- so just inline the max() here + final int measuredWidth = getPageAt(index).getMeasuredWidth(); + final int minWidth = mMinimumWidth; + return (minWidth > measuredWidth) ? minWidth : measuredWidth; + } + + int getPageNearestToCenterOfScreen() { + int minDistanceFromScreenCenter = Integer.MAX_VALUE; + int minDistanceFromScreenCenterIndex = -1; + int screenCenter = getScrollX() + (getMeasuredWidth() / 2); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + View layout = (View) getPageAt(i); + int childWidth = getScaledMeasuredWidth(layout); + 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); + } + + private static class ScrollInterpolator implements Interpolator { + public ScrollInterpolator() { + } + + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t*t*t + 1; + } + } + + // We want the duration of the page snap animation to be influenced by the distance that + // the screen has to travel, however, we don't want this duration to be effected in a + // purely linear fashion. Instead, we use this method to moderate the effect that the distance + // of travel has on the overall snap duration. + float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } + + protected void snapToPageWithVelocity(int whichPage, int velocity) { + whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); + int halfScreenSize = getMeasuredWidth() / 2; + + if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); + if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " + + getMeasuredWidth() + ", " + getChildWidth(whichPage)); + final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + int delta = newX - mUnboundedScrollX; + int duration = 0; + + if (Math.abs(velocity) < mMinFlingVelocity) { + // If the velocity is low enough, then treat this more as an automatic page advance + // as opposed to an apparent physical response to flinging + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + return; + } + + // Here we compute a "distance" that will be used in the computation of the overall + // snap duration. This is a function of the actual distance that needs to be traveled; + // we keep this value close to half screen size in order to reduce the variance in snap + // duration as a function of the distance the page needs to travel. + float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize)); + float distance = halfScreenSize + halfScreenSize * + distanceInfluenceForSnapDuration(distanceRatio); + + velocity = Math.abs(velocity); + velocity = Math.max(mMinSnapVelocity, velocity); + + // we want the page's snap velocity to approximately match the velocity at which the + // user flings, so we scale the duration by a value near to the derivative of the scroll + // interpolator at zero, ie. 5. We use 4 to make it a little slower. + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + + snapToPage(whichPage, delta, duration); + } + + 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)); + + if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); + if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", " + + getChildWidth(whichPage)); + int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + final int sX = mUnboundedScrollX; + 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 == getPageAt(mCurrentPage)) { + focusedChild.clearFocus(); + } + + pageBeginMoving(); + awakenScrollBars(duration); + if (duration == 0) { + duration = Math.abs(delta); + } + + if (!mScroller.isFinished()) mScroller.abortAnimation(); + mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); + + // Load associated pages immediately if someone else is handling the scroll, otherwise defer + // loading associated pages until the scroll settles + if (mDeferScrollUpdate) { + loadAssociatedPages(mNextPage); + } else { + mDeferLoadAssociatedPagesUntilScrollCompletes = true; + } + notifyPageSwitchListener(); + invalidate(); + } + + 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 == getPageAt(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 CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + protected void loadAssociatedPages(int page) { + loadAssociatedPages(page, false); + } + protected void loadAssociatedPages(int page, boolean immediateAndOnly) { + if (mContentIsRefreshable) { + final int count = getChildCount(); + if (page < count) { + int lowerPageBound = getAssociatedLowerPageBound(page); + int upperPageBound = getAssociatedUpperPageBound(page); + if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/" + + upperPageBound); + // First, clear any pages that should no longer be loaded + for (int i = 0; i < count; ++i) { + Page layout = (Page) getPageAt(i); + if ((i < lowerPageBound) || (i > upperPageBound)) { + if (layout.getPageChildCount() > 0) { + layout.removeAllViewsOnPage(); + } + mDirtyPageContent.set(i, true); + } + } + // Next, load any new pages + for (int i = 0; i < count; ++i) { + if ((i != page) && immediateAndOnly) { + continue; + } + if (lowerPageBound <= i && i <= upperPageBound) { + if (mDirtyPageContent.get(i)) { + syncPageItems(i, (i == page) && immediateAndOnly); + mDirtyPageContent.set(i, false); + } + } + } + } + } + } + + 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); + } + + /** + * 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, boolean immediate); + + protected void invalidatePageData() { + invalidatePageData(-1, false); + } + protected void invalidatePageData(int currentPage) { + invalidatePageData(currentPage, false); + } + protected void invalidatePageData(int currentPage, boolean immediateAndOnly) { + if (!mIsDataReady) { + return; + } + + if (mContentIsRefreshable) { + // Force all scrolling-related behavior to end + mScroller.forceFinished(true); + mNextPage = INVALID_PAGE; + + // Update all the pages + syncPages(); + + // We must force a measure after we've loaded the pages to update the content width and + // to determine the full scroll width + measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + + // Set a new page as the current page if necessary + if (currentPage > -1) { + setCurrentPage(Math.min(getPageCount() - 1, currentPage)); + } + + // 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, immediateAndOnly); + requestLayout(); + } + } + + protected View getScrollingIndicator() { + // We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator + // found + if (mHasScrollIndicator && mScrollIndicator == null) { + ViewGroup parent = (ViewGroup) getParent(); + if (parent != null) { + mScrollIndicator = (View) (parent.findViewById(R.id.paged_view_indicator)); + mHasScrollIndicator = mScrollIndicator != null; + if (mHasScrollIndicator) { + mScrollIndicator.setVisibility(View.VISIBLE); + } + } + } + return mScrollIndicator; + } + + protected boolean isScrollingIndicatorEnabled() { + return !LauncherApplication.isScreenLarge(); + } + + Runnable hideScrollingIndicatorRunnable = new Runnable() { + @Override + public void run() { + hideScrollingIndicator(false); + } + }; + protected void flashScrollingIndicator(boolean animated) { + removeCallbacks(hideScrollingIndicatorRunnable); + showScrollingIndicator(!animated); + postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration); + } + + protected void showScrollingIndicator(boolean immediately) { + mShouldShowScrollIndicator = true; + mShouldShowScrollIndicatorImmediately = true; + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + mShouldShowScrollIndicator = false; + getScrollingIndicator(); + if (mScrollIndicator != null) { + // Fade the indicator in + updateScrollingIndicatorPosition(); + mScrollIndicator.setVisibility(View.VISIBLE); + cancelScrollingIndicatorAnimations(); + if (immediately) { + mScrollIndicator.setAlpha(1f); + } else { + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f); + mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration); + mScrollIndicatorAnimator.start(); + } + } + } + + protected void cancelScrollingIndicatorAnimations() { + if (mScrollIndicatorAnimator != null) { + mScrollIndicatorAnimator.cancel(); + } + } + + protected void hideScrollingIndicator(boolean immediately) { + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + getScrollingIndicator(); + if (mScrollIndicator != null) { + // Fade the indicator out + updateScrollingIndicatorPosition(); + cancelScrollingIndicatorAnimations(); + if (immediately) { + mScrollIndicator.setVisibility(View.INVISIBLE); + mScrollIndicator.setAlpha(0f); + } else { + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f); + mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration); + mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { + private boolean cancelled = false; + @Override + public void onAnimationCancel(android.animation.Animator animation) { + cancelled = true; + } + @Override + public void onAnimationEnd(Animator animation) { + if (!cancelled) { + mScrollIndicator.setVisibility(View.INVISIBLE); + } + } + }); + mScrollIndicatorAnimator.start(); + } + } + } + + /** + * To be overridden by subclasses to determine whether the scroll indicator should stretch to + * fill its space on the track or not. + */ + protected boolean hasElasticScrollIndicator() { + return true; + } + + private void updateScrollingIndicator() { + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + getScrollingIndicator(); + if (mScrollIndicator != null) { + updateScrollingIndicatorPosition(); + } + if (mShouldShowScrollIndicator) { + showScrollingIndicator(mShouldShowScrollIndicatorImmediately); + } + } + + private void updateScrollingIndicatorPosition() { + if (!isScrollingIndicatorEnabled()) return; + if (mScrollIndicator == null) return; + int numPages = getChildCount(); + int pageWidth = getMeasuredWidth(); + int lastChildIndex = Math.max(0, getChildCount() - 1); + int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); + int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; + int indicatorWidth = mScrollIndicator.getMeasuredWidth() - + mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight(); + + float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX)); + int indicatorSpace = trackWidth / numPages; + int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft; + if (hasElasticScrollIndicator()) { + if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) { + mScrollIndicator.getLayoutParams().width = indicatorSpace; + mScrollIndicator.requestLayout(); + } + } else { + int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2; + indicatorPos += indicatorCenterOffset; + } + mScrollIndicator.setTranslationX(indicatorPos); + } + + public void showScrollIndicatorTrack() { + } + + public void hideScrollIndicatorTrack() { + } + + /* Accessibility */ + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(getPageCount() > 1); + if (getCurrentPage() < getPageCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (getCurrentPage() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + event.setFromIndex(mCurrentPage); + event.setToIndex(mCurrentPage); + event.setItemCount(getChildCount()); + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (getCurrentPage() < getPageCount() - 1) { + scrollRight(); + return true; + } + } break; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (getCurrentPage() > 0) { + scrollLeft(); + return true; + } + } break; + } + return false; + } + + protected String getCurrentPageDescription() { + return String.format(getContext().getString(R.string.default_scroll_format), + getNextPage() + 1, getChildCount()); + } + + @Override + public boolean onHoverEvent(android.view.MotionEvent event) { + return true; + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewCellLayout.java b/src/com/cyanogenmod/trebuchet/PagedViewCellLayout.java new file mode 100644 index 000000000..5eaf92694 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewCellLayout.java @@ -0,0 +1,516 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; + +import com.cyanogenmod.trebuchet.R; + +/** + * 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 implements Page { + static final String TAG = "PagedViewCellLayout"; + + private int mCellCountX; + private int mCellCountY; + private int mOriginalCellWidth; + private int mOriginalCellHeight; + private int mCellWidth; + private int mCellHeight; + private int mOriginalWidthGap; + private int mOriginalHeightGap; + private int mWidthGap; + private int mHeightGap; + private int mMaxGap; + protected PagedViewCellLayoutChildren mChildren; + + 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 + Resources resources = context.getResources(); + mOriginalCellWidth = mCellWidth = + resources.getDimensionPixelSize(R.dimen.apps_customize_cell_width); + mOriginalCellHeight = mCellHeight = + resources.getDimensionPixelSize(R.dimen.apps_customize_cell_height); + mCellCountX = LauncherModel.getCellCountX(); + mCellCountY = LauncherModel.getCellCountY(); + mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1; + mMaxGap = resources.getDimensionPixelSize(R.dimen.apps_customize_max_gap); + + mChildren = new PagedViewCellLayoutChildren(context); + mChildren.setCellDimensions(mCellWidth, mCellHeight); + mChildren.setGap(mWidthGap, mHeightGap); + + addView(mChildren); + } + + public int getCellWidth() { + return mCellWidth; + } + + public int getCellHeight() { + return mCellHeight; + } + + void destroyHardwareLayers() { + // called when a page is no longer visible (triggered by loadAssociatedPages -> + // removeAllViewsOnPage) + setLayerType(LAYER_TYPE_NONE, null); + } + + void createHardwareLayers() { + // called when a page is visible (triggered by loadAssociatedPages -> syncPageItems) + setLayerType(LAYER_TYPE_HARDWARE, null); + } + + @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); + mChildren.addView(child, index, lp); + + return true; + } + return false; + } + + @Override + public void removeAllViewsOnPage() { + mChildren.removeAllViews(); + destroyHardwareLayers(); + } + + @Override + public void removeViewOnPageAt(int index) { + mChildren.removeViewAt(index); + } + + /** + * Clears all the key listeners for the individual icons. + */ + public void resetChildrenOnKeyListeners() { + int childCount = mChildren.getChildCount(); + for (int j = 0; j < childCount; ++j) { + mChildren.getChildAt(j).setOnKeyListener(null); + } + } + + @Override + public int getPageChildCount() { + return mChildren.getChildCount(); + } + + public PagedViewCellLayoutChildren getChildrenLayout() { + return mChildren; + } + + @Override + public View getChildOnPageAt(int i) { + return mChildren.getChildAt(i); + } + + @Override + public int indexOfChildOnPage(View v) { + return mChildren.indexOfChild(v); + } + + public int getCellCountX() { + return mCellCountX; + } + + public int getCellCountY() { + return mCellCountY; + } + + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + 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"); + } + + int numWidthGaps = mCellCountX - 1; + int numHeightGaps = mCellCountY - 1; + + if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { + int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight(); + int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); + int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth); + int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight); + mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); + mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); + + mChildren.setGap(mWidthGap, mHeightGap); + } else { + mWidthGap = mOriginalWidthGap; + mHeightGap = mOriginalHeightGap; + } + + // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY + int newWidth = widthSpecSize; + int newHeight = heightSpecSize; + if (widthSpecMode == MeasureSpec.AT_MOST) { + newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) + + ((mCellCountX - 1) * mWidthGap); + newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) + + ((mCellCountY - 1) * mHeightGap); + setMeasuredDimension(newWidth, newHeight); + } + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + int childWidthMeasureSpec = + MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() - + getPaddingRight(), MeasureSpec.EXACTLY); + int childheightMeasureSpec = + MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() - + getPaddingBottom(), MeasureSpec.EXACTLY); + child.measure(childWidthMeasureSpec, childheightMeasureSpec); + } + + setMeasuredDimension(newWidth, newHeight); + } + + int getContentWidth() { + return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight(); + } + + int getContentHeight() { + if (mCellCountY > 0) { + return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); + } + return 0; + } + + int getWidthBeforeFirstLayout() { + if (mCellCountX > 0) { + return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap); + } + return 0; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.layout(getPaddingLeft(), getPaddingTop(), + r - l - getPaddingRight(), b - t - getPaddingBottom()); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean result = super.onTouchEvent(event); + int count = getPageChildCount(); + if (count > 0) { + // We only intercept the touch if we are tapping in empty space after the final row + View child = getChildOnPageAt(count - 1); + int bottom = child.getBottom(); + int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX()); + if (numRows < getCellCountY()) { + // Add a little bit of buffer if there is room for another row + bottom += mCellHeight / 2; + } + result = result || (event.getY() < bottom); + } + return result; + } + + public void enableCenteredContent(boolean enabled) { + mChildren.enableCenteredContent(enabled); + } + + @Override + protected void setChildrenDrawingCacheEnabled(boolean enabled) { + mChildren.setChildrenDrawingCacheEnabled(enabled); + } + + public void setCellCount(int xCount, int yCount) { + mCellCountX = xCount; + mCellCountY = yCount; + requestLayout(); + } + + public void setGap(int widthGap, int heightGap) { + mOriginalWidthGap = mWidthGap = widthGap; + mOriginalHeightGap = mHeightGap = heightGap; + mChildren.setGap(widthGap, heightGap); + } + + 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) { + // We don't show the next/previous pages any more, so we use the full width, minus the + // padding + int availWidth = width - (getPaddingLeft() + getPaddingRight()); + + // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N + int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); + + // We don't do anything fancy to determine if we squeeze another row in. + return n; + } + + /** + * Estimates the number of cells that the specified height would take up. + */ + public int estimateCellVSpan(int height) { + // The space for a page is the height - top padding (current page) - bottom padding (current + // page) + int availHeight = height - (getPaddingTop() + getPaddingBottom()); + + // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N + int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); + + // We don't do anything fancy to determine if we squeeze another row in. + return n; + } + + /** Returns an estimated center position of the cell at the specified index */ + public int[] estimateCellPosition(int x, int y) { + return new int[] { + getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2), + getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2) + }; + } + + public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { + mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); + mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); + requestLayout(); + } + + /** + * Estimates the width that the number of hSpan 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; + + if (LauncherApplication.isScreenLarge()) { + x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; + y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; + } else { + x = myCellX * (cellWidth + widthGap) + leftMargin; + y = 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 + ")"; + } + } +} + +interface Page { + public int getPageChildCount(); + public View getChildOnPageAt(int i); + public void removeAllViewsOnPage(); + public void removeViewOnPageAt(int i); + public int indexOfChildOnPage(View v); +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewCellLayoutChildren.java b/src/com/cyanogenmod/trebuchet/PagedViewCellLayoutChildren.java new file mode 100644 index 000000000..3b1a3fb0f --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewCellLayoutChildren.java @@ -0,0 +1,160 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +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 PagedViewCellLayoutChildren extends ViewGroup { + static final String TAG = "PagedViewCellLayout"; + + private boolean mCenterContent; + + private int mCellWidth; + private int mCellHeight; + private int mWidthGap; + private int mHeightGap; + + public PagedViewCellLayoutChildren(Context context) { + super(context); + } + + @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 void setGap(int widthGap, int heightGap) { + mWidthGap = widthGap; + mHeightGap = heightGap; + requestLayout(); + } + + public void setCellDimensions(int width, int height) { + mCellWidth = width; + mCellHeight = height; + requestLayout(); + } + + @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) { + 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 count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + PagedViewCellLayout.LayoutParams lp = + (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); + lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, + getPaddingLeft(), + getPaddingTop()); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, + MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, + MeasureSpec.EXACTLY); + + child.measure(childWidthMeasureSpec, childheightMeasureSpec); + } + + setMeasuredDimension(widthSpecSize, heightSpecSize); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int count = getChildCount(); + + int offsetX = 0; + if (mCenterContent && count > 0) { + // determine the max width of all the rows and center accordingly + int maxRowX = 0; + int minRowX = Integer.MAX_VALUE; + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + PagedViewCellLayout.LayoutParams lp = + (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); + minRowX = Math.min(minRowX, lp.x); + maxRowX = Math.max(maxRowX, lp.x + lp.width); + } + } + int maxRowWidth = maxRowX - minRowX; + offsetX = (getMeasuredWidth() - 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); + } + } + } + + 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); + } + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewGridLayout.java b/src/com/cyanogenmod/trebuchet/PagedViewGridLayout.java new file mode 100644 index 000000000..546912f39 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewGridLayout.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.GridLayout; + +/** + * The grid based layout used strictly for the widget/wallpaper tab of the AppsCustomize pane + */ +public class PagedViewGridLayout extends GridLayout implements Page { + static final String TAG = "PagedViewGridLayout"; + + private int mCellCountX; + private int mCellCountY; + private Runnable mOnLayoutListener; + + public PagedViewGridLayout(Context context, int cellCountX, int cellCountY) { + super(context, null, 0); + mCellCountX = cellCountX; + mCellCountY = cellCountY; + } + + int getCellCountX() { + return mCellCountX; + } + + int getCellCountY() { + return mCellCountY; + } + + /** + * Clears all the key listeners for the individual widgets. + */ + public void resetChildrenOnKeyListeners() { + int childCount = getChildCount(); + for (int j = 0; j < childCount; ++j) { + getChildAt(j).setOnKeyListener(null); + } + } + + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // PagedView currently has issues with different-sized pages since it calculates the + // offset of each page to scroll to before it updates the actual size of each page + // (which can change depending on the content if the contents aren't a fixed size). + // We work around this by having a minimum size on each widget page). + int widthSpecSize = Math.min(getSuggestedMinimumWidth(), + MeasureSpec.getSize(widthMeasureSpec)); + int widthSpecMode = MeasureSpec.EXACTLY; + super.onMeasure(MeasureSpec.makeMeasureSpec(widthSpecSize, widthSpecMode), + heightMeasureSpec); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mOnLayoutListener = null; + } + + public void setOnLayoutListener(Runnable r) { + mOnLayoutListener = r; + } + + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mOnLayoutListener != null) { + mOnLayoutListener.run(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean result = super.onTouchEvent(event); + int count = getPageChildCount(); + if (count > 0) { + // We only intercept the touch if we are tapping in empty space after the final row + View child = getChildOnPageAt(count - 1); + int bottom = child.getBottom(); + result = result || (event.getY() < bottom); + } + return result; + } + + void destroyHardwareLayer() { + setLayerType(LAYER_TYPE_NONE, null); + } + + void createHardwareLayer() { + setLayerType(LAYER_TYPE_HARDWARE, null); + } + + @Override + public void removeAllViewsOnPage() { + removeAllViews(); + mOnLayoutListener = null; + destroyHardwareLayer(); + } + + @Override + public void removeViewOnPageAt(int index) { + removeViewAt(index); + } + + @Override + public int getPageChildCount() { + return getChildCount(); + } + + @Override + public View getChildOnPageAt(int i) { + return getChildAt(i); + } + + @Override + public int indexOfChildOnPage(View v) { + return indexOfChild(v); + } + + public static class LayoutParams extends FrameLayout.LayoutParams { + public LayoutParams(int width, int height) { + super(width, height); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewIcon.java b/src/com/cyanogenmod/trebuchet/PagedViewIcon.java new file mode 100644 index 000000000..6296e67c0 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewIcon.java @@ -0,0 +1,92 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +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 { + /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */ + public static interface PressedCallback { + void iconPressed(PagedViewIcon icon); + } + + @SuppressWarnings("unused") + private static final String TAG = "PagedViewIcon"; + private static final float PRESS_ALPHA = 0.4f; + + private PagedViewIcon.PressedCallback mPressedCallback; + private boolean mLockDrawableState = false; + + private Bitmap mIcon; + + 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); + } + + public void applyFromApplicationInfo(ApplicationInfo info, boolean scaleUp, + PagedViewIcon.PressedCallback cb) { + mIcon = info.iconBitmap; + mPressedCallback = cb; + setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null); + setText(info.title); + setTag(info); + } + + public void lockDrawableState() { + mLockDrawableState = true; + } + + public void resetDrawableState() { + mLockDrawableState = false; + post(new Runnable() { + @Override + public void run() { + refreshDrawableState(); + } + }); + } + + protected void drawableStateChanged() { + super.drawableStateChanged(); + + // We keep in the pressed state until resetDrawableState() is called to reset the press + // feedback + if (isPressed()) { + setAlpha(PRESS_ALPHA); + if (mPressedCallback != null) { + mPressedCallback.iconPressed(this); + } + } else if (!mLockDrawableState) { + setAlpha(1f); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewIconCache.java b/src/com/cyanogenmod/trebuchet/PagedViewIconCache.java new file mode 100644 index 000000000..8086fa709 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewIconCache.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.pm.ComponentInfo; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; + +/** + * Simple cache mechanism for PagedView outlines. + */ +public class PagedViewIconCache { + public static class Key { + public enum Type { + ApplicationInfoKey, + AppWidgetProviderInfoKey, + ResolveInfoKey + } + private final ComponentName mComponentName; + private final Type mType; + + public Key(ApplicationInfo info) { + mComponentName = info.componentName; + mType = Type.ApplicationInfoKey; + } + public Key(ResolveInfo info) { + final ComponentInfo ci = info.activityInfo != null ? info.activityInfo : + info.serviceInfo; + mComponentName = new ComponentName(ci.packageName, ci.name); + mType = Type.ResolveInfoKey; + } + public Key(AppWidgetProviderInfo info) { + mComponentName = info.provider; + mType = Type.AppWidgetProviderInfoKey; + } + + private ComponentName getComponentName() { + return mComponentName; + } + public boolean isKeyType(Type t) { + return (mType == t); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Key) { + Key k = (Key) o; + return mComponentName.equals(k.mComponentName); + } + return super.equals(o); + } + @Override + public int hashCode() { + return getComponentName().hashCode(); + } + } + + private final HashMap mIconOutlineCache = new HashMap(); + + public void clear() { + for (Key key : mIconOutlineCache.keySet()) { + mIconOutlineCache.get(key).recycle(); + } + mIconOutlineCache.clear(); + } + private void retainAll(HashSet keysToKeep, Key.Type t) { + HashSet keysToRemove = new HashSet(mIconOutlineCache.keySet()); + keysToRemove.removeAll(keysToKeep); + for (Key key : keysToRemove) { + if (key.isKeyType(t)) { + mIconOutlineCache.get(key).recycle(); + mIconOutlineCache.remove(key); + } + } + } + /** Removes all the keys to applications that aren't in the passed in collection */ + public void retainAllApps(ArrayList keys) { + HashSet keysSet = new HashSet(); + for (ApplicationInfo info : keys) { + keysSet.add(new Key(info)); + } + retainAll(keysSet, Key.Type.ApplicationInfoKey); + } + /** Removes all the keys to shortcuts that aren't in the passed in collection */ + public void retainAllShortcuts(List keys) { + HashSet keysSet = new HashSet(); + for (ResolveInfo info : keys) { + keysSet.add(new Key(info)); + } + retainAll(keysSet, Key.Type.ResolveInfoKey); + } + /** Removes all the keys to widgets that aren't in the passed in collection */ + public void retainAllAppWidgets(List keys) { + HashSet keysSet = new HashSet(); + for (AppWidgetProviderInfo info : keys) { + keysSet.add(new Key(info)); + } + retainAll(keysSet, Key.Type.AppWidgetProviderInfoKey); + } + public void addOutline(Key key, Bitmap b) { + mIconOutlineCache.put(key, b); + } + public void removeOutline(Key key) { + if (mIconOutlineCache.containsKey(key)) { + mIconOutlineCache.get(key).recycle(); + mIconOutlineCache.remove(key); + } + } + public Bitmap getOutline(Key key) { + return mIconOutlineCache.get(key); + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewWidget.java b/src/com/cyanogenmod/trebuchet/PagedViewWidget.java new file mode 100644 index 000000000..c1dfcdcfb --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewWidget.java @@ -0,0 +1,223 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; + +/** + * The linear layout used strictly for the widget/wallpaper tab of the customization tray + */ +public class PagedViewWidget extends LinearLayout { + static final String TAG = "PagedViewWidgetLayout"; + + private static boolean sDeletePreviewsWhenDetachedFromWindow = true; + + private String mDimensionsFormatString; + CheckForShortPress mPendingCheckForShortPress = null; + ShortPressListener mShortPressListener = null; + boolean mShortPressTriggered = false; + static PagedViewWidget sShortpressTarget = null; + boolean mIsAppWidget; + + public PagedViewWidget(Context context) { + this(context, null); + } + + public PagedViewWidget(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewWidget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final Resources r = context.getResources(); + mDimensionsFormatString = r.getString(R.string.widget_dims_format); + + setWillNotDraw(false); + setClipToPadding(false); + } + + public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) { + sDeletePreviewsWhenDetachedFromWindow = value; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (sDeletePreviewsWhenDetachedFromWindow) { + final ImageView image = (ImageView) findViewById(R.id.widget_preview); + if (image != null) { + FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable(); + if (preview != null && preview.getBitmap() != null) { + preview.getBitmap().recycle(); + } + image.setImageDrawable(null); + } + } + } + + public void applyFromAppWidgetProviderInfo(AppWidgetProviderInfo info, + int maxWidth, int[] cellSpan) { + mIsAppWidget = true; + final ImageView image = (ImageView) findViewById(R.id.widget_preview); + if (maxWidth > -1) { + image.setMaxWidth(maxWidth); + } + image.setContentDescription(info.label); + final TextView name = (TextView) findViewById(R.id.widget_name); + name.setText(info.label); + final TextView dims = (TextView) findViewById(R.id.widget_dims); + if (dims != null) { + int hSpan = Math.min(cellSpan[0], LauncherModel.getCellCountX()); + int vSpan = Math.min(cellSpan[1], LauncherModel.getCellCountY()); + dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); + } + } + + public void applyFromResolveInfo(PackageManager pm, ResolveInfo info) { + mIsAppWidget = false; + CharSequence label = info.loadLabel(pm); + final ImageView image = (ImageView) findViewById(R.id.widget_preview); + image.setContentDescription(label); + final TextView name = (TextView) findViewById(R.id.widget_name); + name.setText(label); + final TextView dims = (TextView) findViewById(R.id.widget_dims); + if (dims != null) { + dims.setText(String.format(mDimensionsFormatString, 1, 1)); + } + } + + public int[] getPreviewSize() { + final ImageView i = (ImageView) findViewById(R.id.widget_preview); + int[] maxSize = new int[2]; + maxSize[0] = i.getWidth() - i.getPaddingLeft() - i.getPaddingRight(); + maxSize[1] = i.getHeight() - i.getPaddingTop(); + return maxSize; + } + + void applyPreview(FastBitmapDrawable preview, int index) { + final PagedViewWidgetImageView image = + (PagedViewWidgetImageView) findViewById(R.id.widget_preview); + if (preview != null) { + image.mAllowRequestLayout = false; + image.setImageDrawable(preview); + if (mIsAppWidget) { + // center horizontally + int[] imageSize = getPreviewSize(); + int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; + image.setPadding(image.getPaddingLeft() + centerAmount, + image.getPaddingTop(), + image.getPaddingRight(), + image.getPaddingBottom()); + } + image.setAlpha(1f); + image.mAllowRequestLayout = true; + } + } + + void setShortPressListener(ShortPressListener listener) { + mShortPressListener = listener; + } + + interface ShortPressListener { + void onShortPress(View v); + void cleanUpShortPress(View v); + } + + class CheckForShortPress implements Runnable { + public void run() { + if (sShortpressTarget != null) return; + if (mShortPressListener != null) { + mShortPressListener.onShortPress(PagedViewWidget.this); + sShortpressTarget = PagedViewWidget.this; + } + mShortPressTriggered = true; + } + } + + private void checkForShortPress() { + if (sShortpressTarget != null) return; + if (mPendingCheckForShortPress == null) { + mPendingCheckForShortPress = new CheckForShortPress(); + } + postDelayed(mPendingCheckForShortPress, 120); + } + + /** + * Remove the longpress detection timer. + */ + private void removeShortPressCallback() { + if (mPendingCheckForShortPress != null) { + removeCallbacks(mPendingCheckForShortPress); + } + } + + private void cleanUpShortPress() { + removeShortPressCallback(); + if (mShortPressTriggered) { + if (mShortPressListener != null) { + mShortPressListener.cleanUpShortPress(PagedViewWidget.this); + } + mShortPressTriggered = false; + } + } + + static void resetShortPressTarget() { + sShortpressTarget = null; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + cleanUpShortPress(); + break; + case MotionEvent.ACTION_DOWN: + checkForShortPress(); + break; + case MotionEvent.ACTION_CANCEL: + cleanUpShortPress(); + break; + case MotionEvent.ACTION_MOVE: + break; + } + + // 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 true; + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewWidgetImageView.java b/src/com/cyanogenmod/trebuchet/PagedViewWidgetImageView.java new file mode 100644 index 000000000..8182b225b --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewWidgetImageView.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Insets; +import android.util.AttributeSet; +import android.widget.ImageView; + + + +class PagedViewWidgetImageView extends ImageView { + public boolean mAllowRequestLayout = true; + + public PagedViewWidgetImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void requestLayout() { + if (mAllowRequestLayout) { + super.requestLayout(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + + Insets insets = Insets.NONE; + if (getBackground() != null) { + insets = getBackground().getLayoutInsets(); + } + canvas.save(); + canvas.clipRect(getScrollX() + getPaddingLeft() + insets.left, + getScrollY() + getPaddingTop() + insets.top, + getScrollX() + getRight() - getLeft() - getPaddingRight() - insets.right, + getScrollY() + getBottom() - getTop() - getPaddingBottom() - insets.bottom); + + super.onDraw(canvas); + canvas.restore(); + + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java b/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java new file mode 100644 index 000000000..694c3c7e2 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PagedViewWithDraggableItems.java @@ -0,0 +1,178 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + + +/* Class that does most of the work of enabling dragging items out of a PagedView by performing a + * vertical drag. Used by both CustomizePagedView and AllAppsPagedView. + * Subclasses must do the following: + * * call setDragSlopeThreshold after making an instance of the PagedViewWithDraggableItems + * * call child.setOnLongClickListener(this) and child.setOnTouchListener(this) on all children + * (good place to do it is in syncPageItems) + * * override beginDragging(View) (but be careful to call super.beginDragging(View) + * + */ +public abstract class PagedViewWithDraggableItems extends PagedView + implements View.OnLongClickListener, View.OnTouchListener { + private View mLastTouchedItem; + private boolean mIsDragging; + private boolean mIsDragEnabled; + private float mDragSlopeThreshold; + private Launcher mLauncher; + + public PagedViewWithDraggableItems(Context context) { + this(context, null); + } + + public PagedViewWithDraggableItems(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedViewWithDraggableItems(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mLauncher = (Launcher) context; + } + + protected boolean beginDragging(View v) { + boolean wasDragging = mIsDragging; + mIsDragging = true; + return !wasDragging; + } + + protected void cancelDragging() { + mIsDragging = false; + mLastTouchedItem = null; + mIsDragEnabled = false; + } + + private void handleTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + cancelDragging(); + mIsDragEnabled = true; + break; + case MotionEvent.ACTION_MOVE: + if (mTouchState != TOUCH_STATE_SCROLLING && !mIsDragging && mIsDragEnabled) { + determineDraggingStart(ev); + } + break; + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + handleTouchEvent(ev); + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + handleTouchEvent(ev); + return super.onTouchEvent(ev); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + mLastTouchedItem = v; + mIsDragEnabled = true; + return false; + } + + @Override + public boolean onLongClick(View v) { + // Return early if this is not initiated from a touch + if (!v.isInTouchMode()) return false; + // Return early if we are still animating the pages + if (mNextPage != INVALID_PAGE) return false; + // When we have exited all apps or are in transition, disregard long clicks + if (!mLauncher.isAllAppsCustomizeOpen() || + mLauncher.getWorkspace().isSwitchingState()) return false; + // Return if global dragging is not enabled + if (!mLauncher.isDraggingEnabled()) return false; + + return beginDragging(v); + } + + /* + * 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) { + if (!mIsDragging) super.determineScrollingStart(ev); + } + + /* + * Determines if we should change the touch state to start dragging after the + * user moves their touch point far enough. + */ + protected void determineDraggingStart(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 yMoved = yDiff > touchSlop; + boolean isUpwardMotion = (yDiff / (float) xDiff) > mDragSlopeThreshold; + + if (isUpwardMotion && yMoved && mLastTouchedItem != null) { + // Drag if the user moved far enough along the Y axis + beginDragging(mLastTouchedItem); + + // Cancel any pending long press + 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(); + } + } + } + } + + public void setDragSlopeThreshold(float dragSlopeThreshold) { + mDragSlopeThreshold = dragSlopeThreshold; + } + + @Override + protected void onDetachedFromWindow() { + cancelDragging(); + super.onDetachedFromWindow(); + } + + /** Show the scrolling indicators when we move the page */ + protected void onPageBeginMoving() { + showScrollingIndicator(false); + } + protected void onPageEndMoving() { + hideScrollingIndicator(false); + } +} diff --git a/src/com/cyanogenmod/trebuchet/PendingAddItemInfo.java b/src/com/cyanogenmod/trebuchet/PendingAddItemInfo.java new file mode 100644 index 000000000..d31c7e199 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PendingAddItemInfo.java @@ -0,0 +1,104 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +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 PendingAddShortcutInfo extends PendingAddItemInfo { + + ActivityInfo shortcutActivityInfo; + + public PendingAddShortcutInfo(ActivityInfo activityInfo) { + shortcutActivityInfo = activityInfo; + } + + @Override + public String toString() { + return "Shortcut: " + shortcutActivityInfo.packageName; + } +} + +class PendingAddWidgetInfo extends PendingAddItemInfo { + int minWidth; + int minHeight; + int minResizeWidth; + int minResizeHeight; + int previewImage; + int icon; + AppWidgetProviderInfo info; + AppWidgetHostView boundWidget; + + // 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; + this.info = i; + componentName = i.provider; + minWidth = i.minWidth; + minHeight = i.minHeight; + minResizeWidth = i.minResizeWidth; + minResizeHeight = i.minResizeHeight; + previewImage = i.previewImage; + icon = i.icon; + if (dataMimeType != null && data != null) { + mimeType = dataMimeType; + configurationData = data; + } + } + + // Copy constructor + public PendingAddWidgetInfo(PendingAddWidgetInfo copy) { + minWidth = copy.minWidth; + minHeight = copy.minHeight; + minResizeWidth = copy.minResizeWidth; + minResizeHeight = copy.minResizeHeight; + previewImage = copy.previewImage; + icon = copy.icon; + info = copy.info; + boundWidget = copy.boundWidget; + mimeType = copy.mimeType; + configurationData = copy.configurationData; + componentName = copy.componentName; + itemType = copy.itemType; + spanX = copy.spanX; + spanY = copy.spanY; + minSpanX = copy.minSpanX; + minSpanY = copy.minSpanY; + } + + @Override + public String toString() { + return "Widget: " + componentName.toShortString(); + } +} diff --git a/src/com/cyanogenmod/trebuchet/PreloadReceiver.java b/src/com/cyanogenmod/trebuchet/PreloadReceiver.java new file mode 100644 index 000000000..0ce8ebecf --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PreloadReceiver.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 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.cyanogenmod.trebuchet; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class PreloadReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final LauncherApplication app = (LauncherApplication) context.getApplicationContext(); + final LauncherProvider provider = app.getLauncherProvider(); + if (provider != null) { + new Thread(new Runnable() { + public void run() { + provider.loadDefaultFavoritesIfNecessary(); + } + }).start(); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/RocketLauncher.java b/src/com/cyanogenmod/trebuchet/RocketLauncher.java new file mode 100644 index 000000000..54da60643 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/RocketLauncher.java @@ -0,0 +1,417 @@ +/*); + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: +// background stellar matter: +// - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift + +package com.cyanogenmod.trebuchet; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeAnimator; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.support.v13.dreams.BasicDream; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.cyanogenmod.trebuchet.R; + +import java.util.HashMap; +import java.util.Random; + +public class RocketLauncher extends BasicDream { + public static final boolean ROCKET_LAUNCHER = true; + + public static class Board extends FrameLayout + { + public static final boolean FIXED_STARS = true; + public static final boolean FLYING_STARS = true; + public static final int NUM_ICONS = 20; + + public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed + private boolean mManeuveringThrusters = false; + private float mSpeedScale = 1.0f; + + public static final int LAUNCH_ZOOM_TIME = 400; // ms + + HashMap mIcons; + ComponentName[] mComponentNames; + + static Random sRNG = new Random(); + + static float lerp(float a, float b, float f) { + return (b-a)*f + a; + } + + static float randfrange(float a, float b) { + return lerp(a, b, sRNG.nextFloat()); + } + + static int randsign() { + return sRNG.nextBoolean() ? 1 : -1; + } + + static E pick(E[] array) { + if (array.length == 0) return null; + return array[sRNG.nextInt(array.length)]; + } + + public class FlyingIcon extends ImageView { + public static final float VMAX = 1000.0f; + public static final float VMIN = 100.0f; + public static final float ANGULAR_VMAX = 45f; + public static final float ANGULAR_VMIN = 0f; + public static final float SCALE_MIN = 0.5f; + public static final float SCALE_MAX = 4f; + + public float v, vr; + + public final float[] hsv = new float[3]; + + public float angle, anglex, angley; + public float fuse; + public float dist; + public float endscale; + public float boardCenterX, boardCenterY; + + public ComponentName component; + + public FlyingIcon(Context context, AttributeSet as) { + super(context, as); + setLayerType(View.LAYER_TYPE_HARDWARE, null); + + setBackgroundResource(R.drawable.flying_icon_bg); + //android.util.Log.d("RocketLauncher", "ctor: " + this); + hsv[1] = 1f; + hsv[2] = 1f; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!mManeuveringThrusters || component == null) { + return false; + } + if (getAlpha() < 0.5f) { + setPressed(false); + return false; + } + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + setPressed(true); + Board.this.resetWarpTimer(); + break; + case MotionEvent.ACTION_MOVE: + final Rect hit = new Rect(); + final Point offset = new Point(); + getGlobalVisibleRect(hit, offset); + final int globx = (int) event.getX() + offset.x; + final int globy = (int) event.getY() + offset.y; + setPressed(hit.contains(globx, globy)); + Board.this.resetWarpTimer(); + break; + case MotionEvent.ACTION_UP: + if (isPressed()) { + setPressed(false); + postDelayed(new Runnable() { + public void run() { + try { + getContext().startActivity(new Intent(Intent.ACTION_MAIN) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .setComponent(component)); + } catch (android.content.ActivityNotFoundException e) { + } catch (SecurityException e) { + } + } + }, LAUNCH_ZOOM_TIME); + endscale = 0; + AnimatorSet s = new AnimatorSet(); + s.playTogether( + ObjectAnimator.ofFloat(this, "scaleX", 15f), + ObjectAnimator.ofFloat(this, "scaleY", 15f), + ObjectAnimator.ofFloat(this, "alpha", 0f) + ); + + // make sure things are still moving until the very last instant the + // activity is visible + s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25)); + s.setInterpolator(new android.view.animation.AccelerateInterpolator(3)); + s.start(); + } + break; + } + return true; + } + + public String toString() { + return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>", + "icon", getX(), getY(), v, angle, dist, fuse); + } + + public void randomizeIcon() { + component = pick(mComponentNames); + setImageBitmap(mIcons.get(component)); + } + + public void randomize() { + v = randfrange(VMIN, VMAX); + angle = randfrange(0, 360f); + anglex = (float) Math.sin(angle / 180. * Math.PI); + angley = (float) Math.cos(angle / 180. * Math.PI); + vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign(); + endscale = randfrange(SCALE_MIN, SCALE_MAX); + + randomizeIcon(); + } + public void reset() { + randomize(); + boardCenterX = (Board.this.getWidth() - getWidth()) / 2; + boardCenterY = (Board.this.getHeight() - getHeight()) / 2; + setX(boardCenterX); + setY(boardCenterY); + fuse = (float) Math.max(boardCenterX, boardCenterY); + setRotation(180-angle); + setScaleX(0f); + setScaleY(0f); + dist = 0; + setAlpha(0f); + } + public void update(float dt) { + dist += v * dt; + setX(getX() + anglex * v * dt); + setY(getY() + angley * v * dt); + //setRotation(getRotation() + vr * dt); + if (endscale > 0) { + float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse)); + setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3))); + setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3))); + final float q1 = fuse*0.15f; + final float q4 = fuse*0.75f; + if (dist < q1) { + setAlpha((float) Math.sqrt(dist/q1)); + } else if (dist > q4) { + setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2))); + } else { + setAlpha(1f); + } + } + } + } + + public class FlyingStar extends FlyingIcon { + public FlyingStar(Context context, AttributeSet as) { + super(context, as); + } + public void randomizeIcon() { + setImageResource(R.drawable.widget_resize_handle_bottom); + } + public void randomize() { + super.randomize(); + v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate + endscale = randfrange(1f, 2f); // ensmallen + } + } + + TimeAnimator mAnim; + + public Board(Context context, AttributeSet as) { + super(context, as); + + setBackgroundColor(0xFF000000); + + LauncherApplication app = (LauncherApplication)context.getApplicationContext(); + mIcons = app.getIconCache().getAllIcons(); + mComponentNames = new ComponentName[mIcons.size()]; + mComponentNames = mIcons.keySet().toArray(mComponentNames); + } + + private void reset() { + removeAllViews(); + + final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + if (FIXED_STARS) { + for(int i=0; i<20; i++) { + ImageView fixedStar = new ImageView(getContext(), null); + fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom); + final float s = randfrange(0.25f, 0.75f); + fixedStar.setScaleX(s); + fixedStar.setScaleY(s); + fixedStar.setAlpha(0.75f); + addView(fixedStar, wrap); + fixedStar.setX(randfrange(0, getWidth())); + fixedStar.setY(randfrange(0, getHeight())); + } + } + + for(int i=0; i MANEUVERING_THRUST_SCALE) { + mSpeedScale -= (2*deltaTime/1000f); + } + if (mSpeedScale < MANEUVERING_THRUST_SCALE) { + mSpeedScale = MANEUVERING_THRUST_SCALE; + } + } else { + if (mSpeedScale < 1.0f) { + mSpeedScale += (deltaTime/1000f); + } + if (mSpeedScale > 1.0f) { + mSpeedScale = 1.0f; + } + } + + for (int i=0; i getWidth() + || nv.getY() + scaledHeight < 0 + || nv.getY() - scaledHeight > getHeight()) + { + nv.reset(); + } + } + } + }); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setLayerType(View.LAYER_TYPE_HARDWARE, null); + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + + reset(); + mAnim.start(); + } + + protected void onSizeChanged (int w, int h, int oldw, int oldh) { + super.onSizeChanged(w,h,oldw,oldh); + mAnim.cancel(); + reset(); + mAnim.start(); + } + + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mAnim.cancel(); + } + + @Override + public boolean isOpaque() { + return true; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + // we want to eat touch events ourselves if we're in warp speed + return (!(ROCKET_LAUNCHER && mManeuveringThrusters)); + } + + final Runnable mEngageWarp = new Runnable() { + @Override + public void run() { + mManeuveringThrusters = false; + } + }; + public void resetWarpTimer() { + final Handler h = getHandler(); + h.removeCallbacks(mEngageWarp); + h.postDelayed(mEngageWarp, 5000); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!ROCKET_LAUNCHER) { + return true; + } + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (!mManeuveringThrusters) { + mManeuveringThrusters = true; + resetWarpTimer(); + return true; + } + } + + return false; + } + } + + @Override + public void onStart() { + super.onStart(); + + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + final int longside = metrics.widthPixels > metrics.heightPixels + ? metrics.widthPixels : metrics.heightPixels; + + Board b = new Board(this, null); + setContentView(b, new ViewGroup.LayoutParams(longside, longside)); + b.setX((metrics.widthPixels - longside) / 2); + b.setY((metrics.heightPixels - longside) / 2); + } + + @Override + public void onUserInteraction() { + if (!ROCKET_LAUNCHER) { + finish(); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/SearchDropTargetBar.java b/src/com/cyanogenmod/trebuchet/SearchDropTargetBar.java new file mode 100644 index 000000000..c58178849 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/SearchDropTargetBar.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.widget.FrameLayout; + +import com.cyanogenmod.trebuchet.R; + +/* + * Ths bar will manage the transition between the QSB search bar and the delete drop + * targets so that each of the individual IconDropTargets don't have to. + */ +public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener { + + private static final int sTransitionInDuration = 200; + private static final int sTransitionOutDuration = 175; + + private ObjectAnimator mDropTargetBarAnim; + private ObjectAnimator mQSBSearchBarAnim; + private static final AccelerateInterpolator sAccelerateInterpolator = + new AccelerateInterpolator(); + + private boolean mIsSearchBarHidden; + private View mQSBSearchBar; + private View mDropTargetBar; + private ButtonDropTarget mInfoDropTarget; + private ButtonDropTarget mDeleteDropTarget; + private int mBarHeight; + private boolean mDeferOnDragEnd = false; + + private Drawable mPreviousBackground; + private boolean mEnableDropDownDropTargets; + + public SearchDropTargetBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchDropTargetBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setup(Launcher launcher, DragController dragController) { + dragController.addDragListener(this); + dragController.addDragListener(mInfoDropTarget); + dragController.addDragListener(mDeleteDropTarget); + dragController.addDropTarget(mInfoDropTarget); + dragController.addDropTarget(mDeleteDropTarget); + dragController.setFlingToDeleteDropTarget(mDeleteDropTarget); + mInfoDropTarget.setLauncher(launcher); + mDeleteDropTarget.setLauncher(launcher); + } + + private void prepareStartAnimation(View v) { + // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd + // callback below) + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + v.buildLayer(); + } + + private void setupAnimation(ObjectAnimator anim, final View v) { + anim.setInterpolator(sAccelerateInterpolator); + anim.setDuration(sTransitionInDuration); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + v.setLayerType(View.LAYER_TYPE_NONE, null); + } + }); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // Get the individual components + mQSBSearchBar = findViewById(R.id.qsb_search_bar); + mDropTargetBar = findViewById(R.id.drag_target_bar); + mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text); + mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text); + mBarHeight = getResources().getDimensionPixelSize(R.dimen.qsb_bar_height); + + mInfoDropTarget.setSearchDropTargetBar(this); + mDeleteDropTarget.setSearchDropTargetBar(this); + + mEnableDropDownDropTargets = + getResources().getBoolean(R.bool.config_useDropTargetDownTransition); + + // Create the various fade animations + if (mEnableDropDownDropTargets) { + mDropTargetBar.setTranslationY(-mBarHeight); + mDropTargetBarAnim = ObjectAnimator.ofFloat(mDropTargetBar, "translationY", + -mBarHeight, 0f); + mQSBSearchBarAnim = ObjectAnimator.ofFloat(mQSBSearchBar, "translationY", 0, + -mBarHeight); + } else { + mDropTargetBar.setAlpha(0f); + mDropTargetBarAnim = ObjectAnimator.ofFloat(mDropTargetBar, "alpha", 0f, 1f); + mQSBSearchBarAnim = ObjectAnimator.ofFloat(mQSBSearchBar, "alpha", 1f, 0f); + } + setupAnimation(mDropTargetBarAnim, mDropTargetBar); + setupAnimation(mQSBSearchBarAnim, mQSBSearchBar); + } + + public void finishAnimations() { + prepareStartAnimation(mDropTargetBar); + mDropTargetBarAnim.reverse(); + prepareStartAnimation(mQSBSearchBar); + mQSBSearchBarAnim.reverse(); + } + + /* + * Shows and hides the search bar. + */ + public void showSearchBar(boolean animated) { + if (!mIsSearchBarHidden) return; + if (animated) { + prepareStartAnimation(mQSBSearchBar); + mQSBSearchBarAnim.reverse(); + } else { + mQSBSearchBarAnim.cancel(); + if (mEnableDropDownDropTargets) { + mQSBSearchBar.setTranslationY(0); + } else { + mQSBSearchBar.setAlpha(1f); + } + } + mIsSearchBarHidden = false; + } + public void hideSearchBar(boolean animated) { + if (mIsSearchBarHidden) return; + if (animated) { + prepareStartAnimation(mQSBSearchBar); + mQSBSearchBarAnim.start(); + } else { + mQSBSearchBarAnim.cancel(); + if (mEnableDropDownDropTargets) { + mQSBSearchBar.setTranslationY(-mBarHeight); + } else { + mQSBSearchBar.setAlpha(0f); + } + } + mIsSearchBarHidden = true; + } + + /* + * Gets various transition durations. + */ + public int getTransitionInDuration() { + return sTransitionInDuration; + } + public int getTransitionOutDuration() { + return sTransitionOutDuration; + } + + /* + * DragController.DragListener implementation + */ + @Override + public void onDragStart(DragSource source, Object info, int dragAction) { + // Animate out the QSB search bar, and animate in the drop target bar + prepareStartAnimation(mDropTargetBar); + mDropTargetBarAnim.start(); + if (!mIsSearchBarHidden) { + prepareStartAnimation(mQSBSearchBar); + mQSBSearchBarAnim.start(); + } + } + + public void deferOnDragEnd() { + mDeferOnDragEnd = true; + } + + @Override + public void onDragEnd() { + if (!mDeferOnDragEnd) { + // Restore the QSB search bar, and animate out the drop target bar + prepareStartAnimation(mDropTargetBar); + mDropTargetBarAnim.reverse(); + if (!mIsSearchBarHidden) { + prepareStartAnimation(mQSBSearchBar); + mQSBSearchBarAnim.reverse(); + } + } else { + mDeferOnDragEnd = false; + } + } + + public void onSearchPackagesChanged(boolean searchVisible, boolean voiceVisible) { + if (mQSBSearchBar != null) { + Drawable bg = mQSBSearchBar.getBackground(); + if (bg != null && (!searchVisible && !voiceVisible)) { + // Save the background and disable it + mPreviousBackground = bg; + mQSBSearchBar.setBackgroundResource(0); + } else if (mPreviousBackground != null && (searchVisible || voiceVisible)) { + // Restore the background + mQSBSearchBar.setBackground(mPreviousBackground); + } + } + } + + public Rect getSearchBarBounds() { + if (mQSBSearchBar != null) { + final float appScale = mQSBSearchBar.getContext().getResources() + .getCompatibilityInfo().applicationScale; + final int[] pos = new int[2]; + mQSBSearchBar.getLocationOnScreen(pos); + + final Rect rect = new Rect(); + rect.left = (int) (pos[0] * appScale + 0.5f); + rect.top = (int) (pos[1] * appScale + 0.5f); + rect.right = (int) ((pos[0] + mQSBSearchBar.getWidth()) * appScale + 0.5f); + rect.bottom = (int) ((pos[1] + mQSBSearchBar.getHeight()) * appScale + 0.5f); + return rect; + } else { + return null; + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/ShortcutAndWidgetContainer.java b/src/com/cyanogenmod/trebuchet/ShortcutAndWidgetContainer.java new file mode 100644 index 000000000..8c10c94e9 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/ShortcutAndWidgetContainer.java @@ -0,0 +1,188 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.app.WallpaperManager; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +public class ShortcutAndWidgetContainer extends ViewGroup { + static final String TAG = "CellLayoutChildren"; + + // 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 WallpaperManager mWallpaperManager; + + private int mCellWidth; + private int mCellHeight; + + private int mWidthGap; + private int mHeightGap; + + public ShortcutAndWidgetContainer(Context context) { + super(context); + mWallpaperManager = WallpaperManager.getInstance(context); + } + + public void enableHardwareLayers() { + setLayerType(LAYER_TYPE_HARDWARE, null); + } + + public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap ) { + mCellWidth = cellWidth; + mCellHeight = cellHeight; + mWidthGap = widthGap; + mHeightGap = heightGap; + } + + public View getChildAt(int x, int y) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + + if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) && + (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) { + return child; + } + } + return null; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + @SuppressWarnings("all") // suppress dead code warning + final boolean debug = false; + if (debug) { + // Debug drawing for hit space + Paint p = new Paint(); + p.setColor(0x6600FF00); + for (int i = getChildCount() - 1; i >= 0; i--) { + final View child = getChildAt(i); + final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + + canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p); + } + } + super.dispatchDraw(canvas); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + measureChild(child); + } + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(widthSpecSize, heightSpecSize); + } + + public void setupLp(CellLayout.LayoutParams lp) { + lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap); + } + + public void measureChild(View child) { + final int cellWidth = mCellWidth; + final int cellHeight = mCellHeight; + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + + lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap); + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, + MeasureSpec.EXACTLY); + child.measure(childWidthMeasureSpec, childheightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); + + int childLeft = lp.x; + int childTop = lp.y; + child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); + + if (lp.dropped) { + lp.dropped = false; + + final int[] cellXY = mTmpCellXY; + getLocationOnScreen(cellXY); + mWallpaperManager.sendWallpaperCommand(getWindowToken(), + WallpaperManager.COMMAND_DROP, + cellXY[0] + childLeft + lp.width / 2, + cellXY[1] + childTop + lp.height / 2, 0, null); + } + } + } + } + + @Override + public boolean shouldDelayChildPressedState() { + 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 + 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(); + } + } + + @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() && enabled) { + view.buildDrawingCache(true); + } + } + } + + @Override + protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { + super.setChildrenDrawnWithCacheEnabled(enabled); + } +} diff --git a/src/com/cyanogenmod/trebuchet/ShortcutInfo.java b/src/com/cyanogenmod/trebuchet/ShortcutInfo.java new file mode 100644 index 000000000..5c77b5f69 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/ShortcutInfo.java @@ -0,0 +1,173 @@ +/* + * 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.cyanogenmod.trebuchet; + +import java.util.ArrayList; + +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Intent; +import android.graphics.Bitmap; +import android.util.Log; + +/** + * Represents a launchable icon on the workspaces and in folders. + */ +class ShortcutInfo extends ItemInfo { + + /** + * The application name. + */ + CharSequence title; + + /** + * The intent used to start the application. + */ + Intent intent; + + /** + * Indicates whether the icon comes from an application's resource (if false) + * or from a custom Bitmap (if true.) + */ + boolean customIcon; + + /** + * Indicates whether we're using the default fallback icon instead of something from the + * app. + */ + boolean usingFallbackIcon; + + /** + * If isShortcut=true and customIcon=false, this contains a reference to the + * shortcut icon as an application's resource. + */ + Intent.ShortcutIconResource iconResource; + + /** + * The application icon. + */ + private Bitmap mIcon; + + ShortcutInfo() { + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; + } + + public ShortcutInfo(ShortcutInfo info) { + super(info); + title = info.title.toString(); + intent = new Intent(info.intent); + if (info.iconResource != null) { + iconResource = new Intent.ShortcutIconResource(); + iconResource.packageName = info.iconResource.packageName; + iconResource.resourceName = info.iconResource.resourceName; + } + mIcon = info.mIcon; // TODO: should make a copy here. maybe we don't need this ctor at all + customIcon = info.customIcon; + } + + /** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */ + public ShortcutInfo(ApplicationInfo info) { + super(info); + title = info.title.toString(); + intent = new Intent(info.intent); + customIcon = false; + } + + public void setIcon(Bitmap b) { + mIcon = b; + } + + public Bitmap getIcon(IconCache iconCache) { + if (mIcon == null) { + updateIcon(iconCache); + } + return mIcon; + } + + /** Returns the package name that the shortcut's intent will resolve to, or an empty string if + * none exists. */ + String getPackageName() { + return super.getPackageName(intent); + } + + public void updateIcon(IconCache iconCache) { + mIcon = iconCache.getIcon(intent); + usingFallbackIcon = iconCache.isDefaultIcon(mIcon); + } + + /** + * Creates the application intent based on a component name and various launch flags. + * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}. + * + * @param className the class name of the component representing the intent + * @param launchFlags the launch flags + */ + final void setActivity(ComponentName className, int launchFlags) { + intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(className); + intent.setFlags(launchFlags); + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION; + } + + @Override + void onAddToDatabase(ContentValues values) { + super.onAddToDatabase(values); + + String titleStr = title != null ? title.toString() : null; + values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr); + + String uri = intent != null ? intent.toUri(0) : null; + values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri); + + if (customIcon) { + values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, + LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP); + writeBitmap(values, mIcon); + } else { + if (!usingFallbackIcon) { + writeBitmap(values, mIcon); + } + values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, + LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE); + if (iconResource != null) { + values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, + iconResource.packageName); + values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE, + iconResource.resourceName); + } + } + } + + @Override + public String toString() { + return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id + + " type=" + this.itemType + " container=" + this.container + " screen=" + screen + + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY + + " isGesture=" + isGesture + " dropPos=" + dropPos + ")"; + } + + public static void dumpShortcutInfoList(String tag, String label, + ArrayList list) { + Log.d(tag, label + " size=" + list.size()); + for (ShortcutInfo info: list) { + Log.d(tag, " title=\"" + info.title + " icon=" + info.mIcon + + " customIcon=" + info.customIcon); + } + } +} + diff --git a/src/com/cyanogenmod/trebuchet/SmoothPagedView.java b/src/com/cyanogenmod/trebuchet/SmoothPagedView.java new file mode 100644 index 000000000..86caf9e25 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/SmoothPagedView.java @@ -0,0 +1,188 @@ +/* + * 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.cyanogenmod.trebuchet; + +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 DEFAULT_MODE = 0; + static final int X_LARGE_MODE = 1; + + int mScrollMode; + + private Interpolator mScrollInterpolator; + + public static class OvershootInterpolator implements Interpolator { + private static final float DEFAULT_TENSION = 1.3f; + private float mTension; + + public OvershootInterpolator() { + 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 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), we only do this in the OVERSHOOT_MODE, ie. on phones + mDeferScrollUpdate = mScrollMode != X_LARGE_MODE; + } + + protected int getScrollMode() { + return X_LARGE_MODE; + } + + /** + * Initializes various states for this workspace. + */ + @Override + protected void init() { + super.init(); + + mScrollMode = getScrollMode(); + if (mScrollMode == DEFAULT_MODE) { + mBaseLineFlingVelocity = 2500.0f; + mFlingVelocityInfluence = 0.4f; + mScrollInterpolator = new OvershootInterpolator(); + mScroller = new Scroller(getContext(), mScrollInterpolator); + } + } + + @Override + protected void snapToDestination() { + if (mScrollMode == X_LARGE_MODE) { + super.snapToDestination(); + } else { + snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0); + } + } + + @Override + protected void snapToPageWithVelocity(int whichPage, int velocity) { + if (mScrollMode == X_LARGE_MODE) { + super.snapToPageWithVelocity(whichPage, velocity); + } else { + snapToPageWithVelocity(whichPage, 0, true); + } + } + + private 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 - mUnboundedScrollX; + int duration = (screenDelta + 1) * 100; + + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + + if (settle) { + ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta); + } else { + ((OvershootInterpolator) 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) { + if (mScrollMode == X_LARGE_MODE) { + super.snapToPage(whichPage); + } else { + snapToPageWithVelocity(whichPage, 0, false); + } + } + + @Override + public void computeScroll() { + if (mScrollMode == X_LARGE_MODE) { + super.computeScroll(); + } else { + 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 - mUnboundedScrollX; + scrollTo(Math.round(mUnboundedScrollX + dx * e), getScrollY()); + 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/cyanogenmod/trebuchet/SpringLoadedDragController.java b/src/com/cyanogenmod/trebuchet/SpringLoadedDragController.java new file mode 100644 index 000000000..e5b27981a --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/SpringLoadedDragController.java @@ -0,0 +1,62 @@ +/* + * 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.cyanogenmod.trebuchet; + +public class SpringLoadedDragController implements OnAlarmListener { + // how long the user must hover over a mini-screen before it unshrinks + final long ENTER_SPRING_LOAD_HOVER_TIME = 500; + final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950; + final long EXIT_SPRING_LOAD_HOVER_TIME = 200; + + Alarm mAlarm; + + // the screen the user is currently hovering over, if any + private CellLayout mScreen; + private Launcher mLauncher; + + public SpringLoadedDragController(Launcher launcher) { + mLauncher = launcher; + mAlarm = new Alarm(); + mAlarm.setOnAlarmListener(this); + } + + public void cancel() { + mAlarm.cancelAlarm(); + } + + // Set a new alarm to expire for the screen that we are hovering over now + public void setAlarm(CellLayout cl) { + mAlarm.cancelAlarm(); + mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME : + ENTER_SPRING_LOAD_HOVER_TIME); + mScreen = cl; + } + + // this is called when our timer runs out + public void onAlarm(Alarm alarm) { + if (mScreen != null) { + // Snap to the screen that we are hovering over now + Workspace w = mLauncher.getWorkspace(); + int page = w.indexOfChild(mScreen); + if (page != w.getCurrentPage()) { + w.snapToPage(page); + } + } else { + mLauncher.getDragController().cancelDrag(); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/StrokedTextView.java b/src/com/cyanogenmod/trebuchet/StrokedTextView.java new file mode 100644 index 000000000..007ddf525 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/StrokedTextView.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +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.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; + +/** + * This class adds a stroke to the generic TextView allowing the text to stand out better against + * the background (ie. in the AllApps button). + */ +public class StrokedTextView extends TextView { + private final Canvas mCanvas = new Canvas(); + private final Paint mPaint = new Paint(); + private Bitmap mCache; + private boolean mUpdateCachedBitmap; + private int mStrokeColor; + private float mStrokeWidth; + private int mTextColor; + + public StrokedTextView(Context context) { + super(context); + init(context, null, 0); + } + + public StrokedTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public StrokedTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StrokedTextView, + defStyle, 0); + mStrokeColor = a.getColor(R.styleable.StrokedTextView_strokeColor, 0xFF000000); + mStrokeWidth = a.getFloat(R.styleable.StrokedTextView_strokeWidth, 0.0f); + mTextColor = a.getColor(R.styleable.StrokedTextView_strokeTextColor, 0xFFFFFFFF); + a.recycle(); + mUpdateCachedBitmap = true; + + // Setup the text paint + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.FILL_AND_STROKE); + } + + protected void onTextChanged(CharSequence text, int start, int before, int after) { + super.onTextChanged(text, start, before, after); + mUpdateCachedBitmap = true; + } + + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (w > 0 && h > 0) { + mUpdateCachedBitmap = true; + mCache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + } else { + mCache = null; + } + } + + protected void onDraw(Canvas canvas) { + if (mCache != null) { + if (mUpdateCachedBitmap) { + final int w = getMeasuredWidth(); + final int h = getMeasuredHeight(); + final String text = getText().toString(); + final Rect textBounds = new Rect(); + final Paint textPaint = getPaint(); + final int textWidth = (int) textPaint.measureText(text); + textPaint.getTextBounds("x", 0, 1, textBounds); + + // Clear the old cached image + mCanvas.setBitmap(mCache); + mCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + + // Draw the drawable + final int drawableLeft = getPaddingLeft(); + final int drawableTop = getPaddingTop(); + final Drawable[] drawables = getCompoundDrawables(); + for (int i = 0; i < drawables.length; ++i) { + if (drawables[i] != null) { + drawables[i].setBounds(drawableLeft, drawableTop, + drawableLeft + drawables[i].getIntrinsicWidth(), + drawableTop + drawables[i].getIntrinsicHeight()); + drawables[i].draw(mCanvas); + } + } + + final int left = w - getPaddingRight() - textWidth; + final int bottom = (h + textBounds.height()) / 2; + + // Draw the outline of the text + mPaint.setStrokeWidth(mStrokeWidth); + mPaint.setColor(mStrokeColor); + mPaint.setTextSize(getTextSize()); + mCanvas.drawText(text, left, bottom, mPaint); + + // Draw the text itself + mPaint.setStrokeWidth(0); + mPaint.setColor(mTextColor); + mCanvas.drawText(text, left, bottom, mPaint); + + mUpdateCachedBitmap = false; + } + canvas.drawBitmap(mCache, 0, 0, mPaint); + } else { + super.onDraw(canvas); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/SymmetricalLinearTween.java b/src/com/cyanogenmod/trebuchet/SymmetricalLinearTween.java new file mode 100644 index 000000000..240305f25 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/SymmetricalLinearTween.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +import android.os.Handler; +import android.os.SystemClock; + +/** + * Provides an animation between 0.0f and 1.0f over a given duration. + */ +class SymmetricalLinearTween { + + private static final int FPS = 30; + private static final int FRAME_TIME = 1000 / FPS; + + Handler mHandler; + int mDuration; + TweenCallback mCallback; + + boolean mRunning; + long mBase; + boolean mDirection; + float mValue; + + /** + * @param duration milliseconds duration + * @param callback callbacks + */ + public SymmetricalLinearTween(boolean initial, int duration, TweenCallback callback) { + mValue = initial ? 1.0f : 0.0f; + mDirection = initial; + mDuration = duration; + mCallback = callback; + mHandler = new Handler(); + } + + /** + * Starts the tweening. + * + * @param direction If direction is true, the value goes towards 1.0f. If direction + * is false, the value goes towards 0.0f. + */ + public void start(boolean direction) { + start(direction, SystemClock.uptimeMillis()); + } + + /** + * Starts the tweening. + * + * @param direction If direction is true, the value goes towards 1.0f. If direction + * is false, the value goes towards 0.0f. + * @param baseTime The time to use as zero for this animation, in the + * {@link SystemClock.uptimeMillis} time base. This allows you to + * synchronize multiple animations. + */ + public void start(boolean direction, long baseTime) { + if (direction != mDirection) { + if (!mRunning) { + mBase = baseTime; + mRunning = true; + mCallback.onTweenStarted(); + long next = SystemClock.uptimeMillis() + FRAME_TIME; + mHandler.postAtTime(mTick, next); + } else { + // reverse direction + long now = SystemClock.uptimeMillis(); + long diff = now - mBase; + mBase = now + diff - mDuration; + } + mDirection = direction; + } + } + + Runnable mTick = new Runnable() { + public void run() { + long base = mBase; + long now = SystemClock.uptimeMillis(); + long diff = now-base; + int duration = mDuration; + float val = diff/(float)duration; + if (!mDirection) { + val = 1.0f - val; + } + if (val > 1.0f) { + val = 1.0f; + } else if (val < 0.0f) { + val = 0.0f; + } + float old = mValue; + mValue = val; + mCallback.onTweenValueChanged(val, old); + int frame = (int)(diff / FRAME_TIME); + long next = base + ((frame+1)*FRAME_TIME); + if (diff < duration) { + mHandler.postAtTime(this, next); + } + if (diff >= duration) { + mCallback.onTweenFinished(); + mRunning = false; + } + } + }; +} + diff --git a/src/com/cyanogenmod/trebuchet/TweenCallback.java b/src/com/cyanogenmod/trebuchet/TweenCallback.java new file mode 100644 index 000000000..88b8dffa2 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/TweenCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet; + +interface TweenCallback { + void onTweenValueChanged(float value, float oldValue); + void onTweenStarted(); + void onTweenFinished(); +} + diff --git a/src/com/cyanogenmod/trebuchet/UninstallShortcutReceiver.java b/src/com/cyanogenmod/trebuchet/UninstallShortcutReceiver.java new file mode 100644 index 000000000..77959d084 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/UninstallShortcutReceiver.java @@ -0,0 +1,166 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.widget.Toast; + +import com.cyanogenmod.trebuchet.R; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class UninstallShortcutReceiver extends BroadcastReceiver { + private static final String ACTION_UNINSTALL_SHORTCUT = + "com.cyanogenmod.trebuchet.action.UNINSTALL_SHORTCUT"; + + // The set of shortcuts that are pending uninstall + private static ArrayList mUninstallQueue = + new ArrayList(); + + // Determines whether to defer uninstalling shortcuts immediately until + // disableAndFlushUninstallQueue() is called. + private static boolean mUseUninstallQueue = false; + + private static class PendingUninstallShortcutInfo { + Intent data; + + public PendingUninstallShortcutInfo(Intent rawData) { + data = rawData; + } + } + + public void onReceive(Context context, Intent data) { + if (!ACTION_UNINSTALL_SHORTCUT.equals(data.getAction())) { + return; + } + + PendingUninstallShortcutInfo info = new PendingUninstallShortcutInfo(data); + if (mUseUninstallQueue) { + mUninstallQueue.add(info); + } else { + processUninstallShortcut(context, info); + } + } + + static void enableUninstallQueue() { + mUseUninstallQueue = true; + } + + static void disableAndFlushUninstallQueue(Context context) { + mUseUninstallQueue = false; + Iterator iter = mUninstallQueue.iterator(); + while (iter.hasNext()) { + processUninstallShortcut(context, iter.next()); + iter.remove(); + } + } + + private static void processUninstallShortcut(Context context, + PendingUninstallShortcutInfo pendingInfo) { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sharedPrefs = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); + + final Intent data = pendingInfo.data; + + LauncherApplication app = (LauncherApplication) context.getApplicationContext(); + synchronized (app) { + removeShortcut(context, data, sharedPrefs); + } + } + + private static void removeShortcut(Context context, Intent data, + final SharedPreferences sharedPrefs) { + Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true); + + if (intent != null && name != null) { + final ContentResolver cr = context.getContentResolver(); + Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, + new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT }, + LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null); + + final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + + boolean changed = false; + + try { + while (c.moveToNext()) { + try { + if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) { + final long id = c.getLong(idIndex); + final Uri uri = LauncherSettings.Favorites.getContentUri(id, false); + cr.delete(uri, null, null); + changed = true; + if (!duplicate) { + break; + } + } + } catch (URISyntaxException e) { + // Ignore + } + } + } finally { + c.close(); + } + + if (changed) { + cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null); + Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name), + Toast.LENGTH_SHORT).show(); + } + + // Remove any items due to be animated + boolean appRemoved; + Set newApps = new HashSet(); + newApps = sharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps); + synchronized (newApps) { + do { + appRemoved = newApps.remove(intent.toUri(0).toString()); + } while (appRemoved); + } + if (appRemoved) { + final Set savedNewApps = newApps; + new Thread("setNewAppsThread-remove") { + public void run() { + synchronized (savedNewApps) { + SharedPreferences.Editor editor = sharedPrefs.edit(); + editor.putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, + savedNewApps); + if (savedNewApps.isEmpty()) { + // Reset the page index if there are no more items + editor.putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1); + } + editor.commit(); + } + } + }.start(); + } + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/Utilities.java b/src/com/cyanogenmod/trebuchet/Utilities.java new file mode 100644 index 000000000..d80cd9dc6 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Utilities.java @@ -0,0 +1,278 @@ +/* + * 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.cyanogenmod.trebuchet; + +import java.util.Random; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.TableMaskFilter; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.util.DisplayMetrics; + +import com.cyanogenmod.trebuchet.R; + +/** + * Various utilities shared amongst the Launcher's classes. + */ +final class Utilities { + @SuppressWarnings("unused") + private static final String TAG = "Launcher.Utilities"; + + private static int sIconWidth = -1; + private static int sIconHeight = -1; + private static int sIconTextureWidth = -1; + private static int sIconTextureHeight = -1; + + private static final Paint sBlurPaint = new Paint(); + private static final Paint sGlowColorPressedPaint = new Paint(); + private static final Paint sGlowColorFocusedPaint = new Paint(); + private static final Paint sDisabledPaint = new Paint(); + private static final Rect sOldBounds = new Rect(); + private static final Canvas sCanvas = new Canvas(); + + static { + sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + } + static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff }; + static int sColorIndex = 0; + + /** + * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS + * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size) + * to the proper size (48dp) + */ + static Bitmap createIconBitmap(Bitmap icon, Context context) { + int textureWidth = sIconTextureWidth; + int textureHeight = sIconTextureHeight; + int sourceWidth = icon.getWidth(); + int sourceHeight = icon.getHeight(); + if (sourceWidth > textureWidth && sourceHeight > textureHeight) { + // Icon is bigger than it should be; clip it (solves the GB->ICS migration case) + return Bitmap.createBitmap(icon, + (sourceWidth - textureWidth) / 2, + (sourceHeight - textureHeight) / 2, + textureWidth, textureHeight); + } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) { + // Icon is the right size, no need to change it + return icon; + } else { + // Icon is too small, render to a larger bitmap + final Resources resources = context.getResources(); + return createIconBitmap(new BitmapDrawable(resources, icon), context); + } + } + + /** + * Returns a bitmap suitable for the all apps view. + */ + static Bitmap createIconBitmap(Drawable icon, Context context) { + synchronized (sCanvas) { // we share the statics :-( + if (sIconWidth == -1) { + initStatics(context); + } + + int width = sIconWidth; + int height = sIconHeight; + + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); + } + } + int sourceWidth = icon.getIntrinsicWidth(); + int sourceHeight = icon.getIntrinsicHeight(); + if (sourceWidth > 0 && sourceHeight > 0) { + // There are intrinsic sizes. + if (width < sourceWidth || height < sourceHeight) { + // It's too big, scale it down. + final float ratio = (float) sourceWidth / sourceHeight; + if (sourceWidth > sourceHeight) { + height = (int) (width / ratio); + } else if (sourceHeight > sourceWidth) { + width = (int) (height * ratio); + } + } else if (sourceWidth < width && sourceHeight < height) { + // Don't scale up the icon + width = sourceWidth; + height = sourceHeight; + } + } + + // no intrinsic size --> use default size + int textureWidth = sIconTextureWidth; + int textureHeight = sIconTextureHeight; + + final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, + Bitmap.Config.ARGB_8888); + final Canvas canvas = sCanvas; + canvas.setBitmap(bitmap); + + final int left = (textureWidth-width) / 2; + final int top = (textureHeight-height) / 2; + + @SuppressWarnings("all") // suppress dead code warning + final boolean debug = false; + if (debug) { + // draw a big box for the icon for debugging + canvas.drawColor(sColors[sColorIndex]); + if (++sColorIndex >= sColors.length) sColorIndex = 0; + Paint debugPaint = new Paint(); + debugPaint.setColor(0xffcccc00); + canvas.drawRect(left, top, left+width, top+height, debugPaint); + } + + sOldBounds.set(icon.getBounds()); + icon.setBounds(left, top, left+width, top+height); + icon.draw(canvas); + icon.setBounds(sOldBounds); + canvas.setBitmap(null); + + return bitmap; + } + } + + static void drawSelectedAllAppsBitmap(Canvas dest, int destWidth, int destHeight, + boolean pressed, Bitmap src) { + synchronized (sCanvas) { // we share the statics :-( + if (sIconWidth == -1) { + // We can't have gotten to here without src being initialized, which + // comes from this file already. So just assert. + //initStatics(context); + throw new RuntimeException("Assertion failed: Utilities not initialized"); + } + + dest.drawColor(0, PorterDuff.Mode.CLEAR); + + int[] xy = new int[2]; + Bitmap mask = src.extractAlpha(sBlurPaint, xy); + + float px = (destWidth - src.getWidth()) / 2; + float py = (destHeight - src.getHeight()) / 2; + dest.drawBitmap(mask, px + xy[0], py + xy[1], + pressed ? sGlowColorPressedPaint : sGlowColorFocusedPaint); + + mask.recycle(); + } + } + + /** + * Returns a Bitmap representing the thumbnail of the specified Bitmap. + * The size of the thumbnail is defined by the dimension + * android.R.dimen.launcher_application_icon_size. + * + * @param bitmap The bitmap to get a thumbnail of. + * @param context The application's context. + * + * @return A thumbnail for the specified bitmap or the bitmap itself if the + * thumbnail could not be created. + */ + static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) { + synchronized (sCanvas) { // we share the statics :-( + if (sIconWidth == -1) { + initStatics(context); + } + + if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) { + return bitmap; + } else { + final Resources resources = context.getResources(); + return createIconBitmap(new BitmapDrawable(resources, bitmap), context); + } + } + } + + static Bitmap drawDisabledBitmap(Bitmap bitmap, Context context) { + synchronized (sCanvas) { // we share the statics :-( + if (sIconWidth == -1) { + initStatics(context); + } + final Bitmap disabled = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888); + final Canvas canvas = sCanvas; + canvas.setBitmap(disabled); + + canvas.drawBitmap(bitmap, 0.0f, 0.0f, sDisabledPaint); + + canvas.setBitmap(null); + + return disabled; + } + } + + private static void initStatics(Context context) { + final Resources resources = context.getResources(); + final DisplayMetrics metrics = resources.getDisplayMetrics(); + final float density = metrics.density; + + sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size); + sIconTextureWidth = sIconTextureHeight = sIconWidth; + + sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL)); + sGlowColorPressedPaint.setColor(0xffffc300); + sGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); + sGlowColorFocusedPaint.setColor(0xffff8e00); + sGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); + + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0.2f); + sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm)); + sDisabledPaint.setAlpha(0x88); + } + + /** Only works for positive numbers. */ + static int roundToPow2(int n) { + int orig = n; + n >>= 1; + int mask = 0x8000000; + while (mask != 0 && (n & mask) == 0) { + mask >>= 1; + } + while (mask != 0) { + n |= mask; + mask >>= 1; + } + n += 1; + if (n != orig) { + n <<= 1; + } + return n; + } + + static int generateRandomId() { + return new Random(System.currentTimeMillis()).nextInt(1 << 24); + } +} diff --git a/src/com/cyanogenmod/trebuchet/WallpaperChooser.java b/src/com/cyanogenmod/trebuchet/WallpaperChooser.java new file mode 100644 index 000000000..d3ca00659 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/WallpaperChooser.java @@ -0,0 +1,47 @@ +/* + * 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.cyanogenmod.trebuchet; + +import com.cyanogenmod.trebuchet.R; + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.os.Bundle; + +public class WallpaperChooser extends Activity { + @SuppressWarnings("unused") + private static final String TAG = "Launcher.WallpaperChooser"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.wallpaper_chooser_base); + + Fragment fragmentView = + getFragmentManager().findFragmentById(R.id.wallpaper_chooser_fragment); + // TODO: The following code is currently not exercised. Leaving it here in case it + // needs to be revived again. + if (fragmentView == null) { + /* When the screen is XLarge, the fragment is not included in the layout, so show it + * as a dialog + */ + DialogFragment fragment = WallpaperChooserDialogFragment.newInstance(); + fragment.show(getFragmentManager(), "dialog"); + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/WallpaperChooserDialogFragment.java b/src/com/cyanogenmod/trebuchet/WallpaperChooserDialogFragment.java new file mode 100644 index 000000000..aa323bf67 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/WallpaperChooserDialogFragment.java @@ -0,0 +1,360 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.WallpaperManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Gallery; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.SpinnerAdapter; + +import com.cyanogenmod.trebuchet.R; + +import java.io.IOException; +import java.util.ArrayList; + +public class WallpaperChooserDialogFragment extends DialogFragment implements + AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { + + private static final String TAG = "Launcher.WallpaperChooserDialogFragment"; + private static final String EMBEDDED_KEY = "com.cyanogenmod.trebuchet." + + "WallpaperChooserDialogFragment.EMBEDDED_KEY"; + + private boolean mEmbedded; + private Bitmap mBitmap = null; + + private ArrayList mThumbs; + private ArrayList mImages; + private WallpaperLoader mLoader; + private WallpaperDrawable mWallpaperDrawable = new WallpaperDrawable(); + + public static WallpaperChooserDialogFragment newInstance() { + WallpaperChooserDialogFragment fragment = new WallpaperChooserDialogFragment(); + fragment.setCancelable(true); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null && savedInstanceState.containsKey(EMBEDDED_KEY)) { + mEmbedded = savedInstanceState.getBoolean(EMBEDDED_KEY); + } else { + mEmbedded = isInLayout(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(EMBEDDED_KEY, mEmbedded); + } + + private void cancelLoader() { + if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) { + mLoader.cancel(true); + mLoader = null; + } + } + + @Override + public void onDetach() { + super.onDetach(); + + cancelLoader(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + cancelLoader(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + /* On orientation changes, the dialog is effectively "dismissed" so this is called + * when the activity is no longer associated with this dying dialog fragment. We + * should just safely ignore this case by checking if getActivity() returns null + */ + Activity activity = getActivity(); + if (activity != null) { + activity.finish(); + } + } + + /* This will only be called when in XLarge mode, since this Fragment is invoked like + * a dialog in that mode + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + findWallpapers(); + + return null; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + findWallpapers(); + + /* If this fragment is embedded in the layout of this activity, then we should + * generate a view to display. Otherwise, a dialog will be created in + * onCreateDialog() + */ + if (mEmbedded) { + View view = inflater.inflate(R.layout.wallpaper_chooser, container, false); + view.setBackground(mWallpaperDrawable); + + final Gallery gallery = (Gallery) view.findViewById(R.id.gallery); + gallery.setCallbackDuringFling(false); + gallery.setOnItemSelectedListener(this); + gallery.setAdapter(new ImageAdapter(getActivity())); + + View setButton = view.findViewById(R.id.set); + setButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + selectWallpaper(gallery.getSelectedItemPosition()); + } + }); + return view; + } + return null; + } + + private void selectWallpaper(int position) { + try { + WallpaperManager wpm = (WallpaperManager) getActivity().getSystemService( + Context.WALLPAPER_SERVICE); + wpm.setResource(mImages.get(position)); + Activity activity = getActivity(); + activity.setResult(Activity.RESULT_OK); + activity.finish(); + } catch (IOException e) { + Log.e(TAG, "Failed to set wallpaper: " + e); + } + } + + // Click handler for the Dialog's GridView + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + selectWallpaper(position); + } + + // Selection handler for the embedded Gallery view + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) { + mLoader.cancel(); + } + mLoader = (WallpaperLoader) new WallpaperLoader().execute(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + + private void findWallpapers() { + mThumbs = new ArrayList(24); + mImages = new ArrayList(24); + + final Resources resources = getResources(); + // Context.getPackageName() may return the "original" package name, + // com.cyanogenmod.trebuchet; Resources needs the real package name, + // com.cyanogenmod.trebuchet. So we ask Resources for what it thinks the + // package name should be. + final String packageName = resources.getResourcePackageName(R.array.wallpapers); + + addWallpapers(resources, packageName, R.array.wallpapers); + addWallpapers(resources, packageName, R.array.extra_wallpapers); + } + + private void addWallpapers(Resources resources, String packageName, int list) { + final String[] extras = resources.getStringArray(list); + for (String extra : extras) { + int res = resources.getIdentifier(extra, "drawable", packageName); + if (res != 0) { + final int thumbRes = resources.getIdentifier(extra + "_small", + "drawable", packageName); + + if (thumbRes != 0) { + mThumbs.add(thumbRes); + mImages.add(res); + // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")"); + } + } + } + } + + private class ImageAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter { + private LayoutInflater mLayoutInflater; + + ImageAdapter(Activity activity) { + mLayoutInflater = activity.getLayoutInflater(); + } + + public int getCount() { + return mThumbs.size(); + } + + public Object getItem(int position) { + return position; + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view; + + if (convertView == null) { + view = mLayoutInflater.inflate(R.layout.wallpaper_item, parent, false); + } else { + view = convertView; + } + + ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image); + + int thumbRes = mThumbs.get(position); + image.setImageResource(thumbRes); + Drawable thumbDrawable = image.getDrawable(); + if (thumbDrawable != null) { + thumbDrawable.setDither(true); + } else { + Log.e(TAG, "Error decoding thumbnail resId=" + thumbRes + " for wallpaper #" + + position); + } + + return view; + } + } + + class WallpaperLoader extends AsyncTask { + BitmapFactory.Options mOptions; + + WallpaperLoader() { + mOptions = new BitmapFactory.Options(); + mOptions.inDither = false; + mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; + } + + @Override + protected Bitmap doInBackground(Integer... params) { + if (isCancelled()) return null; + try { + return BitmapFactory.decodeResource(getResources(), + mImages.get(params[0]), mOptions); + } catch (OutOfMemoryError e) { + return null; + } + } + + @Override + protected void onPostExecute(Bitmap b) { + if (b == null) return; + + if (!isCancelled() && !mOptions.mCancel) { + // Help the GC + if (mBitmap != null) { + mBitmap.recycle(); + } + + View v = getView(); + if (v != null) { + mBitmap = b; + mWallpaperDrawable.setBitmap(b); + v.postInvalidate(); + } else { + mBitmap = null; + mWallpaperDrawable.setBitmap(null); + } + mLoader = null; + } else { + b.recycle(); + } + } + + void cancel() { + mOptions.requestCancelDecode(); + super.cancel(true); + } + } + + /** + * Custom drawable that centers the bitmap fed to it. + */ + static class WallpaperDrawable extends Drawable { + + Bitmap mBitmap; + int mIntrinsicWidth; + int mIntrinsicHeight; + + /* package */void setBitmap(Bitmap bitmap) { + mBitmap = bitmap; + if (mBitmap == null) + return; + mIntrinsicWidth = mBitmap.getWidth(); + mIntrinsicHeight = mBitmap.getHeight(); + } + + @Override + public void draw(Canvas canvas) { + if (mBitmap == null) return; + int width = canvas.getWidth(); + int height = canvas.getHeight(); + int x = (width - mIntrinsicWidth) / 2; + int y = (height - mIntrinsicHeight) / 2; + canvas.drawBitmap(mBitmap, x, y, null); + } + + @Override + public int getOpacity() { + return android.graphics.PixelFormat.OPAQUE; + } + + @Override + public void setAlpha(int alpha) { + // Ignore + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Ignore + } + } +} diff --git a/src/com/cyanogenmod/trebuchet/Workspace.java b/src/com/cyanogenmod/trebuchet/Workspace.java new file mode 100644 index 000000000..38f4abbd4 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/Workspace.java @@ -0,0 +1,3781 @@ +/* + * 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.cyanogenmod.trebuchet; + +import android.animation.Animator; +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.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Camera; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region.Op; +import android.graphics.drawable.Drawable; +import android.os.IBinder; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.TextView; + +import com.cyanogenmod.trebuchet.R; +import com.cyanogenmod.trebuchet.FolderIcon.FolderRingAnimator; +import com.cyanogenmod.trebuchet.LauncherSettings.Favorites; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * 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 SmoothPagedView + implements DropTarget, DragSource, DragScroller, View.OnTouchListener, + DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener { + private static final String TAG = "Launcher.Workspace"; + + // Y rotation to apply to the workspace screens + private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f; + + private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0; + private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; + private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; + + private static final int BACKGROUND_FADE_OUT_DURATION = 350; + private static final int ADJACENT_SCREEN_DROP_DURATION = 300; + private static final int FLING_THRESHOLD_VELOCITY = 500; + + // These animators are used to fade the children's outlines + private ObjectAnimator mChildrenOutlineFadeInAnimation; + private ObjectAnimator mChildrenOutlineFadeOutAnimation; + private float mChildrenOutlineAlpha = 0; + + // These properties refer to the background protection gradient used for AllApps and Customize + private ValueAnimator mBackgroundFadeInAnimation; + private ValueAnimator mBackgroundFadeOutAnimation; + private Drawable mBackground; + boolean mDrawBackground = true; + private float mBackgroundAlpha = 0; + private float mOverScrollMaxBackgroundAlpha = 0.0f; + + private float mWallpaperScrollRatio = 1.0f; + + private final WallpaperManager mWallpaperManager; + private IBinder mWindowToken; + private static final float WALLPAPER_SCREENS_SPAN = 2f; + + private int mDefaultPage; + + /** + * CellInfo for the cell that is currently being dragged + */ + private CellLayout.CellInfo mDragInfo; + + /** + * Target drop area calculated during last acceptDrop call. + */ + private int[] mTargetCell = new int[2]; + private int mDragOverX = -1; + private int mDragOverY = -1; + + static Rect mLandscapeCellLayoutMetrics = null; + static Rect mPortraitCellLayoutMetrics = null; + + /** + * The CellLayout that is currently being dragged over + */ + private CellLayout mDragTargetLayout = null; + /** + * The CellLayout that we will show as glowing + */ + private CellLayout mDragOverlappingLayout = null; + + /** + * The CellLayout which will be dropped to + */ + private CellLayout mDropToLayout = null; + + private Launcher mLauncher; + private IconCache mIconCache; + private DragController mDragController; + + // 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[] mDragViewVisualCenter = new float[2]; + private float[] mTempDragCoordinates = new float[2]; + private float[] mTempCellLayoutCenterCoordinates = new float[2]; + private float[] mTempDragBottomRightCoordinates = new float[2]; + private Matrix mTempInverseMatrix = new Matrix(); + + private SpringLoadedDragController mSpringLoadedDragController; + private float mSpringLoadedShrinkFactor; + + private static final int DEFAULT_CELL_COUNT_X = 4; + private static final int DEFAULT_CELL_COUNT_Y = 4; + + // State variable that indicates whether the pages are small (ie when you're + // in all apps or customize mode) + + enum State { NORMAL, SPRING_LOADED, SMALL }; + private State mState = State.NORMAL; + private boolean mIsSwitchingState = false; + + boolean mAnimatingViewIntoPlace = false; + boolean mIsDragOccuring = false; + boolean mChildrenLayersEnabled = true; + + /** Is the user is dragging an item near the edge of a page? */ + private boolean mInScrollArea = 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 float mOverscrollFade = 0; + private boolean mOverscrollTransformsSet; + public static final int DRAG_BITMAP_PADDING = 2; + private boolean mWorkspaceFadeInAdjacentScreens; + + // Camera and Matrix used to determine the final position of a neighboring CellLayout + private final Matrix mMatrix = new Matrix(); + private final Camera mCamera = new Camera(); + private final float mTempFloat2[] = new float[2]; + + enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM }; + int mWallpaperWidth; + int mWallpaperHeight; + WallpaperOffsetInterpolator mWallpaperOffset; + boolean mUpdateWallpaperOffsetImmediately = false; + private Runnable mDelayedResizeRunnable; + private Runnable mDelayedSnapToPageRunnable; + private Point mDisplaySize = new Point(); + private boolean mIsStaticWallpaper; + private int mWallpaperTravelWidth; + private int mSpringLoadedPageSpacing; + private int mCameraDistance; + + // Variables relating to the creation of user folders by hovering shortcuts over shortcuts + private static final int FOLDER_CREATION_TIMEOUT = 0; + private static final int REORDER_TIMEOUT = 250; + private final Alarm mFolderCreationAlarm = new Alarm(); + private final Alarm mReorderAlarm = new Alarm(); + private FolderRingAnimator mDragFolderRingAnimator = null; + private FolderIcon mDragOverFolderIcon = null; + private boolean mCreateUserFolderOnDrop = false; + private boolean mAddToExistingFolderOnDrop = false; + private DropTarget.DragEnforcer mDragEnforcer; + private float mMaxDistanceForFolderCreation; + + // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) + private float mXDown; + private float mYDown; + final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; + final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; + final static float TOUCH_SLOP_DAMPING_FACTOR = 4; + + // Relating to the animation of items being dropped externally + public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0; + public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1; + public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2; + public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3; + public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4; + + // Related to dragging, folder creation and reordering + private static final int DRAG_MODE_NONE = 0; + private static final int DRAG_MODE_CREATE_FOLDER = 1; + private static final int DRAG_MODE_ADD_TO_FOLDER = 2; + private static final int DRAG_MODE_REORDER = 3; + private int mDragMode = DRAG_MODE_NONE; + private int mLastReorderX = -1; + private int mLastReorderY = -1; + + // These variables are used for storing the initial and final values during workspace animations + private int mSavedScrollX; + private float mSavedRotationY; + private float mSavedTranslationX; + private float mCurrentScaleX; + private float mCurrentScaleY; + private float mCurrentRotationY; + private float mCurrentTranslationX; + private float mCurrentTranslationY; + private float[] mOldTranslationXs; + private float[] mOldTranslationYs; + private float[] mOldScaleXs; + private float[] mOldScaleYs; + private float[] mOldBackgroundAlphas; + private float[] mOldAlphas; + private float[] mNewTranslationXs; + private float[] mNewTranslationYs; + private float[] mNewScaleXs; + private float[] mNewScaleYs; + private float[] mNewBackgroundAlphas; + private float[] mNewAlphas; + private float[] mNewRotationYs; + private float mTransitionProgress; + + /** + * 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 Workspace(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 Workspace(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mContentIsRefreshable = false; + + mDragEnforcer = new DropTarget.DragEnforcer(context); + // With workspace, data is available straight from the get-go + setDataIsReady(); + + final Resources res = getResources(); + mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens); + mFadeInAdjacentScreens = false; + mWallpaperManager = WallpaperManager.getInstance(context); + + int cellCountX = DEFAULT_CELL_COUNT_X; + int cellCountY = DEFAULT_CELL_COUNT_Y; + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.Workspace, defStyle, 0); + + if (LauncherApplication.isScreenLarge()) { + // Determine number of rows/columns dynamically + // TODO: This code currently fails on tablets with an aspect ratio < 1.3. + // Around that ratio we should make cells the same size in portrait and + // landscape + TypedArray actionBarSizeTypedArray = + context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize }); + DisplayMetrics displayMetrics = res.getDisplayMetrics(); + final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f); + final float systemBarHeight = res.getDimension(R.dimen.status_bar_height); + final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp * + displayMetrics.density; + + cellCountX = 1; + while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) { + cellCountX++; + } + + cellCountY = 1; + while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1) + <= smallestScreenDim - systemBarHeight) { + cellCountY++; + } + } + + mSpringLoadedShrinkFactor = + res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; + mSpringLoadedPageSpacing = + res.getDimensionPixelSize(R.dimen.workspace_spring_loaded_page_spacing); + mCameraDistance = res.getInteger(R.integer.config_cameraDistance); + + // if the value is manually specified, use that instead + cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX); + cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY); + mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); + a.recycle(); + + setOnHierarchyChangeListener(this); + + LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); + setHapticFeedbackEnabled(false); + + mLauncher = (Launcher) context; + initWorkspace(); + + // Disable multitouch across the workspace/all apps/customize tray + setMotionEventSplittingEnabled(true); + + // Unless otherwise specified this view is important for accessibility. + if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each + // dimension if unsuccessful + public int[] estimateItemSize(int hSpan, int vSpan, + ItemInfo itemInfo, boolean springLoaded) { + int[] size = new int[2]; + if (getChildCount() > 0) { + CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0); + Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan); + size[0] = r.width(); + size[1] = r.height(); + if (springLoaded) { + size[0] *= mSpringLoadedShrinkFactor; + size[1] *= mSpringLoadedShrinkFactor; + } + return size; + } else { + size[0] = Integer.MAX_VALUE; + size[1] = Integer.MAX_VALUE; + return size; + } + } + public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo, + int hCell, int vCell, int hSpan, int vSpan) { + Rect r = new Rect(); + cl.cellToRect(hCell, vCell, hSpan, vSpan, r); + return r; + } + + public void buildPageHardwareLayers() { + if (getWindowToken() != null) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + CellLayout cl = (CellLayout) getChildAt(i); + cl.getShortcutsAndWidgets().buildLayer(); + } + } + } + + public void onDragStart(DragSource source, Object info, int dragAction) { + mIsDragOccuring = true; + updateChildrenLayersEnabled(); + mLauncher.lockScreenOrientation(); + setChildrenBackgroundAlphaMultipliers(1f); + // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging + InstallShortcutReceiver.enableInstallQueue(); + UninstallShortcutReceiver.enableUninstallQueue(); + } + + public void onDragEnd() { + mIsDragOccuring = false; + updateChildrenLayersEnabled(); + mLauncher.unlockScreenOrientation(false); + + // Re-enable any Un/InstallShortcutReceiver and now process any queued items + InstallShortcutReceiver.disableAndFlushInstallQueue(getContext()); + UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext()); + } + + /** + * Initializes various states for this workspace. + */ + protected void initWorkspace() { + Context context = getContext(); + mCurrentPage = mDefaultPage; + Launcher.setScreen(mCurrentPage); + LauncherApplication app = (LauncherApplication)context.getApplicationContext(); + mIconCache = app.getIconCache(); + setWillNotDraw(false); + setChildrenDrawnWithCacheEnabled(true); + + final Resources res = getResources(); + try { + mBackground = res.getDrawable(R.drawable.apps_customize_bg); + } catch (Resources.NotFoundException e) { + // In this case, we will skip drawing background protection + } + + mWallpaperOffset = new WallpaperOffsetInterpolator(); + Display display = mLauncher.getWindowManager().getDefaultDisplay(); + display.getSize(mDisplaySize); + mWallpaperTravelWidth = (int) (mDisplaySize.x * + wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y)); + + mMaxDistanceForFolderCreation = (0.55f * res.getDimensionPixelSize(R.dimen.app_icon_size)); + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); + } + + @Override + protected int getScrollMode() { + return SmoothPagedView.X_LARGE_MODE; + } + + @Override + public void onChildViewAdded(View parent, View child) { + if (!(child instanceof CellLayout)) { + throw new IllegalArgumentException("A Workspace can only have CellLayout children."); + } + CellLayout cl = ((CellLayout) child); + cl.setOnInterceptTouchListener(this); + cl.setClickable(true); + cl.enableHardwareLayers(); + cl.setContentDescription(getContext().getString( + R.string.workspace_description_format, getChildCount())); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + } + + protected boolean shouldDrawChild(View child) { + final CellLayout cl = (CellLayout) child; + return super.shouldDrawChild(child) && + (cl.getShortcutsAndWidgets().getAlpha() > 0 || + cl.getBackgroundAlpha() > 0); + } + + /** + * @return The open folder on the current screen, or null if there is none + */ + Folder getOpenFolder() { + DragLayer dragLayer = mLauncher.getDragLayer(); + int count = dragLayer.getChildCount(); + for (int i = 0; i < count; i++) { + View child = dragLayer.getChildAt(i); + if (child instanceof Folder) { + Folder folder = (Folder) child; + if (folder.getInfo().opened) + return folder; + } + } + return null; + } + + boolean isTouchActive() { + return mTouchState != TOUCH_STATE_REST; + } + + /** + * Adds the specified child in the specified 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 screen The screen in which to add the child. + * @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 addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) { + addInScreen(child, container, screen, x, y, spanX, spanY, false); + } + + /** + * Adds the specified child in the specified 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 screen The screen in which to add the child. + * @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. + */ + void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY, + boolean insert) { + if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (screen < 0 || screen >= getChildCount()) { + Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() + + " (was " + screen + "); skipping child"); + return; + } + } + + final CellLayout layout; + if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + layout = mLauncher.getHotseat().getLayout(); + child.setOnKeyListener(null); + + // Hide folder title in the hotseat + if (child instanceof FolderIcon) { + ((FolderIcon) child).setTextVisible(false); + } + + if (screen < 0) { + screen = mLauncher.getHotseat().getOrderInHotseat(x, y); + } else { + // Note: We do this to ensure that the hotseat is always laid out in the orientation + // of the hotseat in order regardless of which orientation they were added + x = mLauncher.getHotseat().getCellXFromOrder(screen); + y = mLauncher.getHotseat().getCellYFromOrder(screen); + } + } else { + // Show folder title if not in the hotseat + if (child instanceof FolderIcon) { + ((FolderIcon) child).setTextVisible(true); + } + + layout = (CellLayout) getChildAt(screen); + child.setOnKeyListener(new IconKeyEventListener()); + } + + LayoutParams genericLp = child.getLayoutParams(); + CellLayout.LayoutParams lp; + if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) { + lp = new CellLayout.LayoutParams(x, y, spanX, spanY); + } else { + lp = (CellLayout.LayoutParams) genericLp; + lp.cellX = x; + lp.cellY = y; + lp.cellHSpan = spanX; + lp.cellVSpan = spanY; + } + + if (spanX < 0 && spanY < 0) { + lp.isLockedToGrid = false; + } + + // Get the canonical child id to uniquely represent this view in this screen + int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY); + boolean markCellsAsOccupied = !(child instanceof Folder); + if (!layout.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); + } + } + + /** + * Check if the point (x, y) hits a given page. + */ + private boolean hitsPage(int index, float x, float y) { + final View page = getChildAt(index); + if (page != null) { + float[] localXY = { x, y }; + mapPointFromSelfToChild(page, localXY); + return (localXY[0] >= 0 && localXY[0] < page.getWidth() + && localXY[1] >= 0 && localXY[1] < page.getHeight()); + } + return false; + } + + @Override + protected boolean hitsPreviousPage(float x, float y) { + // mNextPage is set to INVALID_PAGE whenever we are stationary. + // Calculating "next page" this way ensures that you scroll to whatever page you tap on + final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; + + // Only allow tap to next page on large devices, where there's significant margin outside + // the active workspace + return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y); + } + + @Override + protected boolean hitsNextPage(float x, float y) { + // mNextPage is set to INVALID_PAGE whenever we are stationary. + // Calculating "next page" this way ensures that you scroll to whatever page you tap on + final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; + + // Only allow tap to next page on large devices, where there's significant margin outside + // the active workspace + return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y); + } + + /** + * Called directly from a CellLayout (not by the framework), after we've been added as a + * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout + * that it should intercept touch events, which is not something that is normally supported. + */ + @Override + public boolean onTouch(View v, MotionEvent event) { + return (isSmall() || !isFinishedSwitchingState()); + } + + public boolean isSwitchingState() { + return mIsSwitchingState; + } + + /** This differs from isSwitchingState in that we take into account how far the transition + * has completed. */ + public boolean isFinishedSwitchingState() { + return !mIsSwitchingState || (mTransitionProgress > 0.5f); + } + + protected void onWindowVisibilityChanged (int visibility) { + mLauncher.onWindowVisibilityChanged(visibility); + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + if (isSmall() || !isFinishedSwitchingState()) { + // when the home screens are shrunken, shouldn't allow side-scrolling + return false; + } + return super.dispatchUnhandledMove(focused, direction); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + switch (ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mXDown = ev.getX(); + mYDown = ev.getY(); + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_UP: + if (mTouchState == TOUCH_STATE_REST) { + final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); + if (!currentPage.lastDownOnOccupiedCell()) { + onWallpaperTap(ev); + } + } + } + return super.onInterceptTouchEvent(ev); + } + + protected void reinflateWidgetsIfNecessary() { + final int clCount = getChildCount(); + for (int i = 0; i < clCount; i++) { + CellLayout cl = (CellLayout) getChildAt(i); + ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets(); + final int itemCount = swc.getChildCount(); + for (int j = 0; j < itemCount; j++) { + View v = swc.getChildAt(j); + + if (v.getTag() instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); + LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView; + if (lahv != null && lahv.orientationChangedSincedInflation()) { + mLauncher.removeAppWidget(info); + // Remove the current widget which is inflated with the wrong orientation + cl.removeView(lahv); + mLauncher.bindAppWidget(info); + } + } + } + } + } + + @Override + protected void determineScrollingStart(MotionEvent ev) { + if (isSmall()) return; + if (!isFinishedSwitchingState()) return; + + float deltaX = Math.abs(ev.getX() - mXDown); + float deltaY = Math.abs(ev.getY() - mYDown); + + if (Float.compare(deltaX, 0f) == 0) return; + + float slope = deltaY / deltaX; + float theta = (float) Math.atan(slope); + + if (deltaX > mTouchSlop || deltaY > mTouchSlop) { + cancelCurrentPageLongPress(); + } + + if (theta > MAX_SWIPE_ANGLE) { + // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace + return; + } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { + // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to + // increase the touch slop to make it harder to begin scrolling the workspace. This + // results in vertically scrolling widgets to more easily. The higher the angle, the + // more we increase touch slop. + theta -= START_DAMPING_TOUCH_SLOP_ANGLE; + float extraRatio = (float) + Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); + super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); + } else { + // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special + super.determineScrollingStart(ev); + } + } + + @Override + protected boolean isScrollingIndicatorEnabled() { + return super.isScrollingIndicatorEnabled() && (mState != State.SPRING_LOADED); + } + + protected void onPageBeginMoving() { + super.onPageBeginMoving(); + + if (isHardwareAccelerated()) { + updateChildrenLayersEnabled(); + } else { + 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); + } + } + + // Only show page outlines as we pan if we are on large screen + if (LauncherApplication.isScreenLarge()) { + showOutlines(); + mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null; + } + + // If we are not fading in adjacent screens, we still need to restore the alpha in case the + // user scrolls while we are transitioning (should not affect dispatchDraw optimizations) + if (!mWorkspaceFadeInAdjacentScreens) { + for (int i = 0; i < getChildCount(); ++i) { + ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f); + } + } + + // Show the scroll indicator as you pan the page + showScrollingIndicator(false); + } + + protected void onPageEndMoving() { + super.onPageEndMoving(); + + if (isHardwareAccelerated()) { + updateChildrenLayersEnabled(); + } else { + clearChildrenCache(); + } + + + if (mDragController.isDragging()) { + if (isSmall()) { + // If we are in springloaded mode, then force an event to check if the current touch + // is under a new page (to scroll to) + mDragController.forceMoveEvent(); + } + } else { + // If we are not mid-dragging, hide the page outlines if we are on a large screen + if (LauncherApplication.isScreenLarge()) { + hideOutlines(); + } + + // Hide the scroll indicator as you pan the page + if (!mDragController.isDragging()) { + hideScrollingIndicator(false); + } + } + mOverScrollMaxBackgroundAlpha = 0.0f; + + if (mDelayedResizeRunnable != null) { + mDelayedResizeRunnable.run(); + mDelayedResizeRunnable = null; + } + + if (mDelayedSnapToPageRunnable != null) { + mDelayedSnapToPageRunnable.run(); + mDelayedSnapToPageRunnable = null; + } + } + + @Override + protected void notifyPageSwitchListener() { + super.notifyPageSwitchListener(); + Launcher.setScreen(mCurrentPage); + }; + + // As a ratio of screen height, the total distance we want the parallax effect to span + // horizontally + private float wallpaperTravelToScreenWidthRatio(int width, int height) { + float aspectRatio = width / (float) height; + + // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width + // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width + // We will use these two data points to extrapolate how much the wallpaper parallax effect + // to span (ie travel) at any aspect ratio: + + final float ASPECT_RATIO_LANDSCAPE = 16/10f; + final float ASPECT_RATIO_PORTRAIT = 10/16f; + final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; + final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; + + // To find out the desired width at different aspect ratios, we use the following two + // formulas, where the coefficient on x is the aspect ratio (width/height): + // (16/10)x + y = 1.5 + // (10/16)x + y = 1.2 + // We solve for x and y and end up with a final formula: + final float x = + (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / + (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); + final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; + return x * aspectRatio + y; + } + + // The range of scroll values for Workspace + private int getScrollRange() { + return getChildOffset(getChildCount() - 1) - getChildOffset(0); + } + + protected void setWallpaperDimension() { + DisplayMetrics displayMetrics = new DisplayMetrics(); + mLauncher.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics); + final int maxDim = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); + final int minDim = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); + + // We need to ensure that there is enough extra space in the wallpaper for the intended + // parallax effects + if (LauncherApplication.isScreenLarge()) { + mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); + mWallpaperHeight = maxDim; + } else { + mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); + mWallpaperHeight = maxDim; + } + new Thread("setWallpaperDimension") { + public void run() { + mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight); + } + }.start(); + } + + private float wallpaperOffsetForCurrentScroll() { + // Set wallpaper offset steps (1 / (number of screens - 1)) + mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f); + + // For the purposes of computing the scrollRange and overScrollOffset, we assume + // that mLayoutScale is 1. This means that when we're in spring-loaded mode, + // there's no discrepancy between the wallpaper offset for a given page. + float layoutScale = mLayoutScale; + mLayoutScale = 1f; + int scrollRange = getScrollRange(); + + // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale + float adjustedScrollX = Math.max(0, Math.min(getScrollX(), mMaxScrollX)); + adjustedScrollX *= mWallpaperScrollRatio; + mLayoutScale = layoutScale; + + float scrollProgress = + adjustedScrollX / (float) scrollRange; + + if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) { + // The wallpaper travel width is how far, from left to right, the wallpaper will move + // at this orientation. On tablets in portrait mode we don't move all the way to the + // edges of the wallpaper, or otherwise the parallax effect would be too strong. + int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth); + + float offsetInDips = wallpaperTravelWidth * scrollProgress + + (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it + float offset = offsetInDips / (float) mWallpaperWidth; + return offset; + } else { + return scrollProgress; + } + } + + private void syncWallpaperOffsetWithScroll() { + final boolean enableWallpaperEffects = isHardwareAccelerated(); + if (enableWallpaperEffects) { + mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll()); + } + } + + public void updateWallpaperOffsetImmediately() { + mUpdateWallpaperOffsetImmediately = true; + } + + private void updateWallpaperOffsets() { + boolean updateNow = false; + boolean keepUpdating = true; + if (mUpdateWallpaperOffsetImmediately) { + updateNow = true; + keepUpdating = false; + mWallpaperOffset.jumpToFinal(); + mUpdateWallpaperOffsetImmediately = false; + } else { + updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset(); + } + if (updateNow) { + if (mWindowToken != null) { + mWallpaperManager.setWallpaperOffsets(mWindowToken, + mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY()); + } + } + if (keepUpdating) { + invalidate(); + } + } + + @Override + protected void updateCurrentPageScroll() { + super.updateCurrentPageScroll(); + computeWallpaperScrollRatio(mCurrentPage); + } + + @Override + protected void snapToPage(int whichPage) { + super.snapToPage(whichPage); + computeWallpaperScrollRatio(whichPage); + } + + @Override + protected void snapToPage(int whichPage, int duration) { + super.snapToPage(whichPage, duration); + computeWallpaperScrollRatio(whichPage); + } + + protected void snapToPage(int whichPage, Runnable r) { + if (mDelayedSnapToPageRunnable != null) { + mDelayedSnapToPageRunnable.run(); + } + mDelayedSnapToPageRunnable = r; + snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION); + } + + private void computeWallpaperScrollRatio(int page) { + // Here, we determine what the desired scroll would be with and without a layout scale, + // and compute a ratio between the two. This allows us to adjust the wallpaper offset + // as though there is no layout scale. + float layoutScale = mLayoutScale; + int scaled = getChildOffset(page) - getRelativeChildOffset(page); + mLayoutScale = 1.0f; + float unscaled = getChildOffset(page) - getRelativeChildOffset(page); + mLayoutScale = layoutScale; + if (scaled > 0) { + mWallpaperScrollRatio = (1.0f * unscaled) / scaled; + } else { + mWallpaperScrollRatio = 1f; + } + } + + class WallpaperOffsetInterpolator { + float mFinalHorizontalWallpaperOffset = 0.0f; + float mFinalVerticalWallpaperOffset = 0.5f; + float mHorizontalWallpaperOffset = 0.0f; + float mVerticalWallpaperOffset = 0.5f; + long mLastWallpaperOffsetUpdateTime; + boolean mIsMovingFast; + boolean mOverrideHorizontalCatchupConstant; + float mHorizontalCatchupConstant = 0.35f; + float mVerticalCatchupConstant = 0.35f; + + public WallpaperOffsetInterpolator() { + } + + public void setOverrideHorizontalCatchupConstant(boolean override) { + mOverrideHorizontalCatchupConstant = override; + } + + public void setHorizontalCatchupConstant(float f) { + mHorizontalCatchupConstant = f; + } + + public void setVerticalCatchupConstant(float f) { + mVerticalCatchupConstant = f; + } + + public boolean computeScrollOffset() { + if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 && + Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) { + mIsMovingFast = false; + return false; + } + boolean isLandscape = mDisplaySize.x > mDisplaySize.y; + + long currentTime = System.currentTimeMillis(); + long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime; + timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate); + timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate); + + float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset); + if (!mIsMovingFast && xdiff > 0.07) { + mIsMovingFast = true; + } + + float fractionToCatchUpIn1MsHorizontal; + if (mOverrideHorizontalCatchupConstant) { + fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant; + } else if (mIsMovingFast) { + fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f; + } else { + // slow + fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f; + } + float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant; + + fractionToCatchUpIn1MsHorizontal /= 33f; + fractionToCatchUpIn1MsVertical /= 33f; + + final float UPDATE_THRESHOLD = 0.00001f; + float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset; + float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset; + boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD && + Math.abs(vOffsetDelta) < UPDATE_THRESHOLD; + + // Don't have any lag between workspace and wallpaper on non-large devices + if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) { + mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; + mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; + } else { + float percentToCatchUpVertical = + Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical); + float percentToCatchUpHorizontal = + Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal); + mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta; + mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta; + } + + mLastWallpaperOffsetUpdateTime = System.currentTimeMillis(); + return true; + } + + public float getCurrX() { + return mHorizontalWallpaperOffset; + } + + public float getFinalX() { + return mFinalHorizontalWallpaperOffset; + } + + public float getCurrY() { + return mVerticalWallpaperOffset; + } + + public float getFinalY() { + return mFinalVerticalWallpaperOffset; + } + + public void setFinalX(float x) { + mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f)); + } + + public void setFinalY(float y) { + mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f)); + } + + public void jumpToFinal() { + mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; + mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; + } + } + + @Override + public void computeScroll() { + super.computeScroll(); + syncWallpaperOffsetWithScroll(); + } + + void showOutlines() { + if (!isSmall() && !mIsSwitchingState) { + if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); + if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); + mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f); + mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION); + mChildrenOutlineFadeInAnimation.start(); + } + } + + void hideOutlines() { + if (!isSmall() && !mIsSwitchingState) { + if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); + if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); + mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f); + mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION); + mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY); + mChildrenOutlineFadeOutAnimation.start(); + } + } + + public void showOutlinesTemporarily() { + if (!mIsPageMoving && !isTouchActive()) { + snapToPage(mCurrentPage); + } + } + + public void setChildrenOutlineAlpha(float alpha) { + mChildrenOutlineAlpha = alpha; + for (int i = 0; i < getChildCount(); i++) { + CellLayout cl = (CellLayout) getChildAt(i); + cl.setBackgroundAlpha(alpha); + } + } + + public float getChildrenOutlineAlpha() { + return mChildrenOutlineAlpha; + } + + void disableBackground() { + mDrawBackground = false; + } + void enableBackground() { + mDrawBackground = true; + } + + private void animateBackgroundGradient(float finalAlpha, boolean animated) { + if (mBackground == null) return; + if (mBackgroundFadeInAnimation != null) { + mBackgroundFadeInAnimation.cancel(); + mBackgroundFadeInAnimation = null; + } + if (mBackgroundFadeOutAnimation != null) { + mBackgroundFadeOutAnimation.cancel(); + mBackgroundFadeOutAnimation = null; + } + float startAlpha = getBackgroundAlpha(); + if (finalAlpha != startAlpha) { + if (animated) { + mBackgroundFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha); + mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue()); + } + }); + mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f)); + mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION); + mBackgroundFadeOutAnimation.start(); + } else { + setBackgroundAlpha(finalAlpha); + } + } + } + + public void setBackgroundAlpha(float alpha) { + if (alpha != mBackgroundAlpha) { + mBackgroundAlpha = alpha; + invalidate(); + } + } + + public float getBackgroundAlpha() { + return mBackgroundAlpha; + } + + /** + * Due to 3D transformations, if two CellLayouts are theoretically touching each other, + * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived + * as being larger. This method computes what offset the rotated view should be translated + * in order to minimize this perceived gap. + * @param degrees Angle of the view + * @param width Width of the view + * @param height Height of the view + * @return Offset to be used in a View.setTranslationX() call + */ + private float getOffsetXForRotation(float degrees, int width, int height) { + mMatrix.reset(); + mCamera.save(); + mCamera.rotateY(Math.abs(degrees)); + mCamera.getMatrix(mMatrix); + mCamera.restore(); + + mMatrix.preTranslate(-width * 0.5f, -height * 0.5f); + mMatrix.postTranslate(width * 0.5f, height * 0.5f); + mTempFloat2[0] = width; + mTempFloat2[1] = height; + mMatrix.mapPoints(mTempFloat2); + return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f); + } + + float backgroundAlphaInterpolator(float r) { + float pivotA = 0.1f; + float pivotB = 0.4f; + if (r < pivotA) { + return 0; + } else if (r > pivotB) { + return 1.0f; + } else { + return (r - pivotA)/(pivotB - pivotA); + } + } + + float overScrollBackgroundAlphaInterpolator(float r) { + float threshold = 0.08f; + + if (r > mOverScrollMaxBackgroundAlpha) { + mOverScrollMaxBackgroundAlpha = r; + } else if (r < mOverScrollMaxBackgroundAlpha) { + r = mOverScrollMaxBackgroundAlpha; + } + + return Math.min(r / threshold, 1.0f); + } + + private void updatePageAlphaValues(int screenCenter) { + boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; + if (mWorkspaceFadeInAdjacentScreens && + mState == State.NORMAL && + !mIsSwitchingState && + !isInOverscroll) { + for (int i = 0; i < getChildCount(); i++) { + CellLayout child = (CellLayout) getChildAt(i); + if (child != null) { + float scrollProgress = getScrollProgress(screenCenter, child, i); + float alpha = 1 - Math.abs(scrollProgress); + child.getShortcutsAndWidgets().setAlpha(alpha); + if (!mIsDragOccuring) { + child.setBackgroundAlphaMultiplier( + backgroundAlphaInterpolator(Math.abs(scrollProgress))); + } else { + child.setBackgroundAlphaMultiplier(1f); + } + } + } + } + } + + private void setChildrenBackgroundAlphaMultipliers(float a) { + for (int i = 0; i < getChildCount(); i++) { + CellLayout child = (CellLayout) getChildAt(i); + child.setBackgroundAlphaMultiplier(a); + } + } + + @Override + protected void screenScrolled(int screenCenter) { + super.screenScrolled(screenCenter); + + updatePageAlphaValues(screenCenter); + + if (mOverScrollX < 0 || mOverScrollX > mMaxScrollX) { + int index = mOverScrollX < 0 ? 0 : getChildCount() - 1; + CellLayout cl = (CellLayout) getChildAt(index); + float scrollProgress = getScrollProgress(screenCenter, cl, index); + cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0); + float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress; + cl.setRotationY(rotation); + setFadeForOverScroll(Math.abs(scrollProgress)); + if (!mOverscrollTransformsSet) { + mOverscrollTransformsSet = true; + cl.setCameraDistance(mDensity * mCameraDistance); + cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f)); + cl.setPivotY(cl.getMeasuredHeight() * 0.5f); + cl.setOverscrollTransformsDirty(true); + } + } else { + if (mOverscrollFade != 0) { + setFadeForOverScroll(0); + } + if (mOverscrollTransformsSet) { + mOverscrollTransformsSet = false; + ((CellLayout) getChildAt(0)).resetOverscrollTransforms(); + ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms(); + } + } + } + + @Override + protected void overScroll(float amount) { + acceleratedOverScroll(amount); + } + + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mWindowToken = getWindowToken(); + computeScroll(); + mDragController.setWindowToken(mWindowToken); + } + + protected void onDetachedFromWindow() { + mWindowToken = null; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + mUpdateWallpaperOffsetImmediately = true; + } + super.onLayout(changed, left, top, right, bottom); + } + + @Override + protected void onDraw(Canvas canvas) { + updateWallpaperOffsets(); + + // Draw the background gradient if necessary + if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) { + int alpha = (int) (mBackgroundAlpha * 255); + mBackground.setAlpha(alpha); + mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(), + getMeasuredHeight()); + mBackground.draw(canvas); + } + + super.onDraw(canvas); + } + + boolean isDrawingBackgroundGradient() { + return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + if (!mLauncher.isAllAppsVisible()) { + final Folder openFolder = getOpenFolder(); + if (openFolder != null) { + return openFolder.requestFocus(direction, previouslyFocusedRect); + } else { + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + } + return false; + } + + @Override + public int getDescendantFocusability() { + if (isSmall()) { + return ViewGroup.FOCUS_BLOCK_DESCENDANTS; + } + return super.getDescendantFocusability(); + } + + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + if (!mLauncher.isAllAppsVisible()) { + final Folder openFolder = getOpenFolder(); + if (openFolder != null) { + openFolder.addFocusables(views, direction); + } else { + super.addFocusables(views, direction, focusableMode); + } + } + } + + public boolean isSmall() { + return mState == State.SMALL || mState == State.SPRING_LOADED; + } + + void enableChildrenCache(int fromPage, int toPage) { + if (fromPage > toPage) { + final int temp = fromPage; + fromPage = toPage; + toPage = temp; + } + + final int screenCount = getChildCount(); + + fromPage = Math.max(fromPage, 0); + toPage = Math.min(toPage, screenCount - 1); + + for (int i = fromPage; i <= toPage; i++) { + final CellLayout layout = (CellLayout) getChildAt(i); + layout.setChildrenDrawnWithCacheEnabled(true); + layout.setChildrenDrawingCacheEnabled(true); + } + } + + void clearChildrenCache() { + final int screenCount = getChildCount(); + for (int i = 0; i < screenCount; i++) { + final CellLayout layout = (CellLayout) getChildAt(i); + layout.setChildrenDrawnWithCacheEnabled(false); + // In software mode, we don't want the items to continue to be drawn into bitmaps + if (!isHardwareAccelerated()) { + layout.setChildrenDrawingCacheEnabled(false); + } + } + } + + private void updateChildrenLayersEnabled() { + boolean small = mState == State.SMALL || mIsSwitchingState; + boolean enableChildrenLayers = small || mAnimatingViewIntoPlace || isPageMoving(); + + if (enableChildrenLayers != mChildrenLayersEnabled) { + mChildrenLayersEnabled = enableChildrenLayers; + for (int i = 0; i < getPageCount(); i++) { + ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(mChildrenLayersEnabled); + } + } + } + + protected void onWallpaperTap(MotionEvent ev) { + final int[] position = mTempCell; + getLocationOnScreen(position); + + int pointerIndex = ev.getActionIndex(); + position[0] += (int) ev.getX(pointerIndex); + position[1] += (int) ev.getY(pointerIndex); + + mWallpaperManager.sendWallpaperCommand(getWindowToken(), + ev.getAction() == MotionEvent.ACTION_UP + ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, + position[0], position[1], 0, null); + } + + /* + * This interpolator emulates the rate at which the perceived scale of an object changes + * as its distance from a camera increases. When this interpolator is applied to a scale + * animation on a view, it evokes the sense that the object is shrinking due to moving away + * from the camera. + */ + static class ZInterpolator implements TimeInterpolator { + private float focalLength; + + public ZInterpolator(float foc) { + focalLength = foc; + } + + public float getInterpolation(float input) { + return (1.0f - focalLength / (focalLength + input)) / + (1.0f - focalLength / (focalLength + 1.0f)); + } + } + + /* + * The exact reverse of ZInterpolator. + */ + static class InverseZInterpolator implements TimeInterpolator { + private ZInterpolator zInterpolator; + public InverseZInterpolator(float foc) { + zInterpolator = new ZInterpolator(foc); + } + public float getInterpolation(float input) { + return 1 - zInterpolator.getInterpolation(1 - input); + } + } + + /* + * ZInterpolator compounded with an ease-out. + */ + static class ZoomOutInterpolator implements TimeInterpolator { + private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f); + private final ZInterpolator zInterpolator = new ZInterpolator(0.13f); + + public float getInterpolation(float input) { + return decelerate.getInterpolation(zInterpolator.getInterpolation(input)); + } + } + + /* + * InvereZInterpolator compounded with an ease-out. + */ + static class ZoomInInterpolator implements TimeInterpolator { + private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f); + private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f); + + public float getInterpolation(float input) { + return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input)); + } + } + + private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator(); + + /* + * + * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) 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). + * + */ + public void onDragStartedWithItem(View v) { + final Canvas canvas = new Canvas(); + + // The outline is used to visualize where the item will land if dropped + mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING); + } + + public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, Paint alphaClipPaint) { + final Canvas canvas = new Canvas(); + + int[] size = estimateItemSize(info.spanX, info.spanY, info, false); + + // The outline is used to visualize where the item will land if dropped + mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0], + size[1], alphaClipPaint); + } + + public void exitWidgetResizeMode() { + DragLayer dragLayer = mLauncher.getDragLayer(); + dragLayer.clearAllResizeFrames(); + } + + private void initAnimationArrays() { + final int childCount = getChildCount(); + if (mOldTranslationXs != null) return; + mOldTranslationXs = new float[childCount]; + mOldTranslationYs = new float[childCount]; + mOldScaleXs = new float[childCount]; + mOldScaleYs = new float[childCount]; + mOldBackgroundAlphas = new float[childCount]; + mOldAlphas = new float[childCount]; + mNewTranslationXs = new float[childCount]; + mNewTranslationYs = new float[childCount]; + mNewScaleXs = new float[childCount]; + mNewScaleYs = new float[childCount]; + mNewBackgroundAlphas = new float[childCount]; + mNewAlphas = new float[childCount]; + mNewRotationYs = new float[childCount]; + } + + Animator getChangeStateAnimation(final State state, boolean animated) { + return getChangeStateAnimation(state, animated, 0); + } + + Animator getChangeStateAnimation(final State state, boolean animated, int delay) { + if (mState == state) { + return null; + } + + // Initialize animation arrays for the first time if necessary + initAnimationArrays(); + + AnimatorSet anim = animated ? new AnimatorSet() : null; + + // Stop any scrolling, move to the current page right away + setCurrentPage(getNextPage()); + + final State oldState = mState; + final boolean oldStateIsNormal = (oldState == State.NORMAL); + final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED); + final boolean oldStateIsSmall = (oldState == State.SMALL); + mState = state; + final boolean stateIsNormal = (state == State.NORMAL); + final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED); + final boolean stateIsSmall = (state == State.SMALL); + float finalScaleFactor = 1.0f; + float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f; + float translationX = 0; + float translationY = 0; + boolean zoomIn = true; + + if (state != State.NORMAL) { + finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0); + setPageSpacing(mSpringLoadedPageSpacing); + if (oldStateIsNormal && stateIsSmall) { + zoomIn = false; + setLayoutScale(finalScaleFactor); + updateChildrenLayersEnabled(); + } else { + finalBackgroundAlpha = 1.0f; + setLayoutScale(finalScaleFactor); + } + } else { + setPageSpacing(PagedView.AUTOMATIC_PAGE_SPACING); + setLayoutScale(1.0f); + } + + final int duration = zoomIn ? + getResources().getInteger(R.integer.config_workspaceUnshrinkTime) : + getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); + for (int i = 0; i < getChildCount(); i++) { + final CellLayout cl = (CellLayout) getChildAt(i); + float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded || + (i == mCurrentPage)) ? 1f : 0f; + float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); + float initialAlpha = currentAlpha; + + // Determine the pages alpha during the state transition + if ((oldStateIsSmall && stateIsNormal) || + (oldStateIsNormal && stateIsSmall)) { + // To/from workspace - only show the current page unless the transition is not + // animated and the animation end callback below doesn't run; + // or, if we're in spring-loaded mode + if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) { + finalAlpha = 1f; + } else { + initialAlpha = 0f; + finalAlpha = 0f; + } + } + + mOldAlphas[i] = initialAlpha; + mNewAlphas[i] = finalAlpha; + if (animated) { + mOldTranslationXs[i] = cl.getTranslationX(); + mOldTranslationYs[i] = cl.getTranslationY(); + mOldScaleXs[i] = cl.getScaleX(); + mOldScaleYs[i] = cl.getScaleY(); + mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); + + mNewTranslationXs[i] = translationX; + mNewTranslationYs[i] = translationY; + mNewScaleXs[i] = finalScaleFactor; + mNewScaleYs[i] = finalScaleFactor; + mNewBackgroundAlphas[i] = finalBackgroundAlpha; + } else { + cl.setTranslationX(translationX); + cl.setTranslationY(translationY); + cl.setScaleX(finalScaleFactor); + cl.setScaleY(finalScaleFactor); + cl.setBackgroundAlpha(finalBackgroundAlpha); + cl.setShortcutAndWidgetAlpha(finalAlpha); + } + } + + if (animated) { + for (int index = 0; index < getChildCount(); index++) { + final int i = index; + final CellLayout cl = (CellLayout) getChildAt(i); + float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); + if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) { + cl.setTranslationX(mNewTranslationXs[i]); + cl.setTranslationY(mNewTranslationYs[i]); + cl.setScaleX(mNewScaleXs[i]); + cl.setScaleY(mNewScaleYs[i]); + cl.setBackgroundAlpha(mNewBackgroundAlphas[i]); + cl.setShortcutAndWidgetAlpha(mNewAlphas[i]); + cl.setRotationY(mNewRotationYs[i]); + } else { + LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl); + a.translationX(mNewTranslationXs[i]) + .translationY(mNewTranslationYs[i]) + .scaleX(mNewScaleXs[i]) + .scaleY(mNewScaleYs[i]) + .setDuration(duration) + .setInterpolator(mZoomInInterpolator); + anim.play(a); + + if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) { + LauncherViewPropertyAnimator alphaAnim = + new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets()); + alphaAnim.alpha(mNewAlphas[i]) + .setDuration(duration) + .setInterpolator(mZoomInInterpolator); + anim.play(alphaAnim); + } + if (mOldBackgroundAlphas[i] != 0 || + mNewBackgroundAlphas[i] != 0) { + ValueAnimator bgAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration); + bgAnim.setInterpolator(mZoomInInterpolator); + bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { + public void onAnimationUpdate(float a, float b) { + cl.setBackgroundAlpha( + a * mOldBackgroundAlphas[i] + + b * mNewBackgroundAlphas[i]); + } + }); + anim.play(bgAnim); + } + } + } + buildPageHardwareLayers(); + anim.setStartDelay(delay); + } + + if (stateIsSpringLoaded) { + // Right now we're covered by Apps Customize + // Show the background gradient immediately, so the gradient will + // be showing once AppsCustomize disappears + animateBackgroundGradient(getResources().getInteger( + R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false); + } else { + // Fade the background gradient away + animateBackgroundGradient(0f, true); + } + return anim; + } + + @Override + public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { + mIsSwitchingState = true; + cancelScrollingIndicatorAnimations(); + } + + @Override + public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { + } + + @Override + public void onLauncherTransitionStep(Launcher l, float t) { + mTransitionProgress = t; + } + + @Override + public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { + mIsSwitchingState = false; + mWallpaperOffset.setOverrideHorizontalCatchupConstant(false); + updateChildrenLayersEnabled(); + // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure + // ensure that only the current page is visible during (and subsequently, after) the + // transition animation. If fade adjacent pages is disabled, then re-enable the page + // visibility after the transition animation. + if (!mWorkspaceFadeInAdjacentScreens) { + for (int i = 0; i < getChildCount(); i++) { + final CellLayout cl = (CellLayout) getChildAt(i); + cl.setShortcutAndWidgetAlpha(1f); + } + } + } + + @Override + public View getContent() { + return this; + } + + /** + * 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, boolean pruneToDrawable) { + final Rect clipRect = mTempRect; + v.getDrawingRect(clipRect); + + boolean textVisible = false; + + destCanvas.save(); + if (v instanceof TextView && pruneToDrawable) { + Drawable d = ((TextView) v).getCompoundDrawables()[1]; + clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding); + destCanvas.translate(padding / 2, padding / 2); + d.draw(destCanvas); + } else { + if (v instanceof FolderIcon) { + // For FolderIcons the text can bleed into the icon area, and so we need to + // hide the text completely (which can't be achieved by clipping). + if (((FolderIcon) v).getTextVisible()) { + ((FolderIcon) v).setTextVisible(false); + textVisible = true; + } + } else if (v instanceof BubbleTextView) { + final BubbleTextView tv = (BubbleTextView) v; + clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + + tv.getLayout().getLineTop(0); + } else if (v instanceof TextView) { + final TextView tv = (TextView) v; + clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() + + tv.getLayout().getLineTop(0); + } + destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); + destCanvas.clipRect(clipRect, Op.REPLACE); + v.draw(destCanvas); + + // Restore text visibility of FolderIcon if necessary + if (textVisible) { + ((FolderIcon) v).setTextVisible(true); + } + } + destCanvas.restore(); + } + + /** + * Returns a new bitmap to show when the given View is being dragged around. + * Responsibility for the bitmap is transferred to the caller. + */ + public Bitmap createDragBitmap(View v, Canvas canvas, int padding) { + Bitmap b; + + if (v instanceof TextView) { + Drawable d = ((TextView) v).getCompoundDrawables()[1]; + b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding, + d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888); + } else { + b = Bitmap.createBitmap( + v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); + } + + canvas.setBitmap(b); + drawDragView(v, canvas, padding, true); + canvas.setBitmap(null); + + return b; + } + + /** + * 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(android.R.color.holo_blue_light); + final Bitmap b = Bitmap.createBitmap( + v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + drawDragView(v, canvas, padding, true); + mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); + canvas.setBitmap(null); + return b; + } + + /** + * 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(Bitmap orig, Canvas canvas, int padding, int w, int h, + Paint alphaClipPaint) { + final int outlineColor = getResources().getColor(android.R.color.holo_blue_light); + final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + canvas.setBitmap(b); + + Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); + float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), + (h - padding) / (float) orig.getHeight()); + int scaledWidth = (int) (scaleFactor * orig.getWidth()); + int scaledHeight = (int) (scaleFactor * orig.getHeight()); + Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); + + // center the image + dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); + + canvas.drawBitmap(orig, src, dst, null); + mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor, + alphaClipPaint); + canvas.setBitmap(null); + + 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; + child.setVisibility(INVISIBLE); + CellLayout layout = (CellLayout) child.getParent().getParent(); + layout.prepareChildForDrag(child); + + child.clearFocus(); + child.setPressed(false); + + final Canvas canvas = new Canvas(); + + // The outline is used to visualize where the item will land if dropped + mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); + beginDragShared(child, this); + } + + public void beginDragShared(View child, DragSource source) { + Resources r = getResources(); + + // The drag bitmap follows the touch point around on the screen + final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING); + + final int bmpWidth = b.getWidth(); + final int bmpHeight = b.getHeight(); + + mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); + int dragLayerX = + Math.round(mTempXY[0] - (bmpWidth - child.getScaleX() * child.getWidth()) / 2); + int dragLayerY = + Math.round(mTempXY[1] - (bmpHeight - child.getScaleY() * bmpHeight) / 2 + - DRAG_BITMAP_PADDING / 2); + + Point dragVisualizeOffset = null; + Rect dragRect = null; + if (child instanceof BubbleTextView || child instanceof PagedViewIcon) { + int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size); + int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top); + int top = child.getPaddingTop(); + int left = (bmpWidth - iconSize) / 2; + int right = left + iconSize; + int bottom = top + iconSize; + dragLayerY += top; + // Note: The drag region is used to calculate drag layer offsets, but the + // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. + dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, + iconPaddingTop - DRAG_BITMAP_PADDING / 2); + dragRect = new Rect(left, top, right, bottom); + } else if (child instanceof FolderIcon) { + int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size); + dragRect = new Rect(0, 0, child.getWidth(), previewSize); + } + + // Clear the pressed state if necessary + if (child instanceof BubbleTextView) { + BubbleTextView icon = (BubbleTextView) child; + icon.clearPressedOrFocusedBackground(); + } + + mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), + DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, child.getScaleX()); + b.recycle(); + + // Show the scrolling indicator when you pick up an item + showScrollingIndicator(false); + } + + void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen, + int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) { + View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info); + + final int[] cellXY = new int[2]; + target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); + addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); + LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0], + cellXY[1]); + } + + public boolean transitionStateShouldAllowDrop() { + return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL); + } + + /** + * {@inheritDoc} + */ + public boolean acceptDrop(DragObject d) { + // If it's an external drop (e.g. from All Apps), check if it should be accepted + CellLayout dropTargetLayout = mDropToLayout; + if (d.dragSource != this) { + // Don't accept the drop if we're not over a screen at time of drop + if (dropTargetLayout == null) { + return false; + } + if (!transitionStateShouldAllowDrop()) return false; + + mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, + d.dragView, mDragViewVisualCenter); + + // We want the point to be mapped to the dragTarget. + if (mLauncher.isHotseatLayout(dropTargetLayout)) { + mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); + } else { + mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); + } + + int spanX = 1; + int spanY = 1; + if (mDragInfo != null) { + final CellLayout.CellInfo dragCellInfo = mDragInfo; + spanX = dragCellInfo.spanX; + spanY = dragCellInfo.spanY; + } else { + final ItemInfo dragInfo = (ItemInfo) d.dragInfo; + spanX = dragInfo.spanX; + spanY = dragInfo.spanY; + } + + int minSpanX = spanX; + int minSpanY = spanY; + if (d.dragInfo instanceof PendingAddWidgetInfo) { + minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX; + minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY; + } + + mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout, + mTargetCell); + float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], + mDragViewVisualCenter[1], mTargetCell); + if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, + mTargetCell, distance, true)) { + return true; + } + if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout, + mTargetCell, distance)) { + return true; + } + + int[] resultSpan = new int[2]; + mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, + null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); + boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; + + // Don't accept the drop if there's no room for the item + if (!foundCell) { + // Don't show the message if we are dropping on the AllApps button and the hotseat + // is full + boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout); + if (mTargetCell != null && isHotseat) { + Hotseat hotseat = mLauncher.getHotseat(); + if (hotseat.isAllAppsButtonRank( + hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) { + return false; + } + } + + mLauncher.showOutOfSpaceMessage(isHotseat); + return false; + } + } + return true; + } + + boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float + distance, boolean considerTimeout) { + if (distance > mMaxDistanceForFolderCreation) return false; + View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); + + if (dropOverView != null) { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); + if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { + return false; + } + } + + boolean hasntMoved = false; + if (mDragInfo != null) { + hasntMoved = dropOverView == mDragInfo.cell; + } + + if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { + return false; + } + + boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); + boolean willBecomeShortcut = + (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || + info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); + + return (aboveShortcut && willBecomeShortcut); + } + + boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, + float distance) { + if (distance > mMaxDistanceForFolderCreation) return false; + View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); + + if (dropOverView != null) { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); + if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) { + return false; + } + } + + if (dropOverView instanceof FolderIcon) { + FolderIcon fi = (FolderIcon) dropOverView; + if (fi.acceptDrop(dragInfo)) { + return true; + } + } + return false; + } + + boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, + int[] targetCell, float distance, boolean external, DragView dragView, + Runnable postAnimationRunnable) { + if (distance > mMaxDistanceForFolderCreation) return false; + View v = target.getChildAt(targetCell[0], targetCell[1]); + + boolean hasntMoved = false; + if (mDragInfo != null) { + CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); + hasntMoved = (mDragInfo.cellX == targetCell[0] && + mDragInfo.cellY == targetCell[1]) && (cellParent == target); + } + + if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; + mCreateUserFolderOnDrop = false; + final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target); + + boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); + boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); + + if (aboveShortcut && willBecomeShortcut) { + ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); + ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); + // if the drag started here, we need to remove it from the workspace + if (!external) { + getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); + } + + Rect folderLocation = new Rect(); + float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); + target.removeView(v); + + FolderIcon fi = + mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]); + destInfo.cellX = -1; + destInfo.cellY = -1; + sourceInfo.cellX = -1; + sourceInfo.cellY = -1; + + // If the dragView is null, we can't animate + boolean animate = dragView != null; + if (animate) { + fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, + postAnimationRunnable); + } else { + fi.addItem(destInfo); + fi.addItem(sourceInfo); + } + return true; + } + return false; + } + + boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, + float distance, DragObject d, boolean external) { + if (distance > mMaxDistanceForFolderCreation) return false; + + View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); + if (!mAddToExistingFolderOnDrop) return false; + mAddToExistingFolderOnDrop = false; + + if (dropOverView instanceof FolderIcon) { + FolderIcon fi = (FolderIcon) dropOverView; + if (fi.acceptDrop(d.dragInfo)) { + fi.onDrop(d); + + // if the drag started here, we need to remove it from the workspace + if (!external) { + getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); + } + return true; + } + } + return false; + } + + public void onDrop(final DragObject d) { + mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, + mDragViewVisualCenter); + + CellLayout dropTargetLayout = mDropToLayout; + + // We want the point to be mapped to the dragTarget. + if (dropTargetLayout != null) { + if (mLauncher.isHotseatLayout(dropTargetLayout)) { + mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); + } else { + mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); + } + } + + int snapScreen = -1; + boolean resizeOnDrop = false; + if (d.dragSource != this) { + final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1] }; + onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); + } else if (mDragInfo != null) { + final View cell = mDragInfo.cell; + + Runnable resizeRunnable = null; + if (dropTargetLayout != null) { + // Move internally + boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); + boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); + long container = hasMovedIntoHotseat ? + LauncherSettings.Favorites.CONTAINER_HOTSEAT : + LauncherSettings.Favorites.CONTAINER_DESKTOP; + int screen = (mTargetCell[0] < 0) ? + mDragInfo.screen : indexOfChild(dropTargetLayout); + int spanX = mDragInfo != null ? mDragInfo.spanX : 1; + int spanY = mDragInfo != null ? mDragInfo.spanY : 1; + // First we find the cell nearest to point at which the item is + // dropped, without any consideration to whether there is an item there. + + mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) + mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); + float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], + mDragViewVisualCenter[1], mTargetCell); + + // If the item being dropped is a shortcut and the nearest drop + // cell also contains a shortcut, then create a folder with the two shortcuts. + if (!mInScrollArea && createUserFolderIfNecessary(cell, container, + dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) { + return; + } + + if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, + distance, d, false)) { + return; + } + + // Aside from the special case where we're dropping a shortcut onto a shortcut, + // we need to find the nearest cell location that is vacant + ItemInfo item = (ItemInfo) d.dragInfo; + int minSpanX = item.spanX; + int minSpanY = item.spanY; + if (item.minSpanX > 0 && item.minSpanY > 0) { + minSpanX = item.minSpanX; + minSpanY = item.minSpanY; + } + + int[] resultSpan = new int[2]; + mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, + mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); + + boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; + if (foundCell && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) { + resizeOnDrop = true; + item.spanX = resultSpan[0]; + item.spanY = resultSpan[1]; + AppWidgetHostView awhv = (AppWidgetHostView) cell; + AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], + resultSpan[1]); + } + + if (mCurrentPage != screen && !hasMovedIntoHotseat) { + snapScreen = screen; + snapToPage(screen); + } + + if (foundCell) { + final ItemInfo info = (ItemInfo) cell.getTag(); + if (hasMovedLayouts) { + // Reparent the view + getParentCellLayoutForView(cell).removeView(cell); + addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1], + info.spanX, info.spanY); + } + + // update the item's position after drop + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); + lp.cellX = lp.tmpCellX = mTargetCell[0]; + lp.cellY = lp.tmpCellY = mTargetCell[1]; + lp.cellHSpan = item.spanX; + lp.cellVSpan = item.spanY; + lp.isLockedToGrid = true; + cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen, + mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); + + if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && + cell instanceof LauncherAppWidgetHostView) { + final CellLayout cellLayout = dropTargetLayout; + // We post this call so that the widget has a chance to be placed + // in its final location + + final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; + AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); + if (pinfo != null && + pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { + final Runnable addResizeFrame = new Runnable() { + public void run() { + DragLayer dragLayer = mLauncher.getDragLayer(); + dragLayer.addResizeFrame(info, hostView, cellLayout); + } + }; + resizeRunnable = (new Runnable() { + public void run() { + if (!isPageMoving()) { + addResizeFrame.run(); + } else { + mDelayedResizeRunnable = addResizeFrame; + } + } + }); + } + } + + LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX, + lp.cellY); + } else { + // If we can't find a drop location, we return the item to its original position + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); + mTargetCell[0] = lp.cellX; + mTargetCell[1] = lp.cellY; + CellLayout layout = (CellLayout) cell.getParent().getParent(); + layout.markCellsAsOccupiedForView(cell); + } + } + + final CellLayout parent = (CellLayout) cell.getParent().getParent(); + final Runnable finalResizeRunnable = resizeRunnable; + // Prepare it to be animated into its new position + // This must be called after the view has been re-parented + final Runnable onCompleteRunnable = new Runnable() { + @Override + public void run() { + mAnimatingViewIntoPlace = false; + updateChildrenLayersEnabled(); + if (finalResizeRunnable != null) { + finalResizeRunnable.run(); + } + } + }; + mAnimatingViewIntoPlace = true; + if (d.dragView.hasDrawn()) { + final ItemInfo info = (ItemInfo) cell.getTag(); + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) { + int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE : + ANIMATE_INTO_POSITION_AND_DISAPPEAR; + animateWidgetDrop(info, parent, d.dragView, + onCompleteRunnable, animationType, cell, false); + } else { + int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, + onCompleteRunnable, this); + } + } else { + d.deferDragViewCleanupPostAnimation = false; + cell.setVisibility(VISIBLE); + } + parent.onDropChild(cell); + } + } + + public void setFinalScrollForPageChange(int screen) { + if (screen >= 0) { + mSavedScrollX = getScrollX(); + CellLayout cl = (CellLayout) getChildAt(screen); + mSavedTranslationX = cl.getTranslationX(); + mSavedRotationY = cl.getRotationY(); + final int newX = getChildOffset(screen) - getRelativeChildOffset(screen); + setScrollX(newX); + cl.setTranslationX(0f); + cl.setRotationY(0f); + } + } + + public void resetFinalScrollForPageChange(int screen) { + if (screen >= 0) { + CellLayout cl = (CellLayout) getChildAt(screen); + setScrollX(mSavedScrollX); + cl.setTranslationX(mSavedTranslationX); + cl.setRotationY(mSavedRotationY); + } + } + + public void getViewLocationRelativeToSelf(View v, int[] location) { + getLocationInWindow(location); + int x = location[0]; + int y = location[1]; + + v.getLocationInWindow(location); + int vX = location[0]; + int vY = location[1]; + + location[0] = vX - x; + location[1] = vY - y; + } + + public void onDragEnter(DragObject d) { + mDragEnforcer.onDragEnter(); + mCreateUserFolderOnDrop = false; + mAddToExistingFolderOnDrop = false; + + mDropToLayout = null; + CellLayout layout = getCurrentDropLayout(); + setCurrentDropLayout(layout); + setCurrentDragOverlappingLayout(layout); + + // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we + // don't need to show the outlines + if (LauncherApplication.isScreenLarge()) { + showOutlines(); + } + } + + static Rect getCellLayoutMetrics(Launcher launcher, int orientation) { + Resources res = launcher.getResources(); + Display display = launcher.getWindowManager().getDefaultDisplay(); + Point smallestSize = new Point(); + Point largestSize = new Point(); + display.getCurrentSizeRange(smallestSize, largestSize); + if (orientation == CellLayout.LANDSCAPE) { + if (mLandscapeCellLayoutMetrics == null) { + int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land); + int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land); + int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land); + int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land); + int width = largestSize.x - paddingLeft - paddingRight; + int height = smallestSize.y - paddingTop - paddingBottom; + mLandscapeCellLayoutMetrics = new Rect(); + CellLayout.getMetrics(mLandscapeCellLayoutMetrics, res, + width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(), + orientation); + } + return mLandscapeCellLayoutMetrics; + } else if (orientation == CellLayout.PORTRAIT) { + if (mPortraitCellLayoutMetrics == null) { + int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land); + int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land); + int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land); + int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land); + int width = smallestSize.x - paddingLeft - paddingRight; + int height = largestSize.y - paddingTop - paddingBottom; + mPortraitCellLayoutMetrics = new Rect(); + CellLayout.getMetrics(mPortraitCellLayoutMetrics, res, + width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(), + orientation); + } + return mPortraitCellLayoutMetrics; + } + return null; + } + + public void onDragExit(DragObject d) { + mDragEnforcer.onDragExit(); + + // Here we store the final page that will be dropped to, if the workspace in fact + // receives the drop + if (mInScrollArea) { + mDropToLayout = mDragOverlappingLayout; + } else { + mDropToLayout = mDragTargetLayout; + } + + if (mDragMode == DRAG_MODE_CREATE_FOLDER) { + mCreateUserFolderOnDrop = true; + } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) { + mAddToExistingFolderOnDrop = true; + } + + // Reset the scroll area and previous drag target + onResetScrollArea(); + setCurrentDropLayout(null); + setCurrentDragOverlappingLayout(null); + + mSpringLoadedDragController.cancel(); + + if (!mIsPageMoving) { + hideOutlines(); + } + } + + void setCurrentDropLayout(CellLayout layout) { + if (mDragTargetLayout != null) { + mDragTargetLayout.revertTempState(); + mDragTargetLayout.onDragExit(); + } + mDragTargetLayout = layout; + if (mDragTargetLayout != null) { + mDragTargetLayout.onDragEnter(); + } + cleanupReorder(true); + cleanupFolderCreation(); + setCurrentDropOverCell(-1, -1); + } + + void setCurrentDragOverlappingLayout(CellLayout layout) { + if (mDragOverlappingLayout != null) { + mDragOverlappingLayout.setIsDragOverlapping(false); + } + mDragOverlappingLayout = layout; + if (mDragOverlappingLayout != null) { + mDragOverlappingLayout.setIsDragOverlapping(true); + } + invalidate(); + } + + void setCurrentDropOverCell(int x, int y) { + if (x != mDragOverX || y != mDragOverY) { + mDragOverX = x; + mDragOverY = y; + setDragMode(DRAG_MODE_NONE); + } + } + + void setDragMode(int dragMode) { + if (dragMode != mDragMode) { + if (dragMode == DRAG_MODE_NONE) { + cleanupAddToFolder(); + // We don't want to cancel the re-order alarm every time the target cell changes + // as this feels to slow / unresponsive. + cleanupReorder(false); + cleanupFolderCreation(); + } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) { + cleanupReorder(true); + cleanupFolderCreation(); + } else if (dragMode == DRAG_MODE_CREATE_FOLDER) { + cleanupAddToFolder(); + cleanupReorder(true); + } else if (dragMode == DRAG_MODE_REORDER) { + cleanupAddToFolder(); + cleanupFolderCreation(); + } + mDragMode = dragMode; + } + } + + private void cleanupFolderCreation() { + if (mDragFolderRingAnimator != null) { + mDragFolderRingAnimator.animateToNaturalState(); + } + mFolderCreationAlarm.cancelAlarm(); + } + + private void cleanupAddToFolder() { + if (mDragOverFolderIcon != null) { + mDragOverFolderIcon.onDragExit(null); + mDragOverFolderIcon = null; + } + } + + private void cleanupReorder(boolean cancelAlarm) { + // Any pending reorders are canceled + if (cancelAlarm) { + mReorderAlarm.cancelAlarm(); + } + mLastReorderX = -1; + mLastReorderY = -1; + } + + public DropTarget getDropTargetDelegate(DragObject d) { + return 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. + * + */ + 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; + } + int scrollX = getScrollX(); + if (mNextPage != INVALID_PAGE) { + scrollX = mScroller.getFinalX(); + } + xy[0] = xy[0] + scrollX - v.getLeft(); + xy[1] = xy[1] + getScrollY() - v.getTop(); + cachedInverseMatrix.mapPoints(xy); + } + + /* + * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace + * covers the full screen) + */ + void mapPointFromSelfToSibling(View v, float[] xy) { + xy[0] = xy[0] - v.getLeft(); + xy[1] = xy[1] - v.getTop(); + } + + void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { + xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft(); + xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop(); + } + + /* + * + * 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); + int scrollX = getScrollX(); + if (mNextPage != INVALID_PAGE) { + scrollX = mScroller.getFinalX(); + } + xy[0] -= (scrollX - v.getLeft()); + xy[1] -= (getScrollY() - 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; + draggedItemTopLeft[1] = dragViewY; + final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; + draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth(); + draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight(); + + // 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; + } + + /* + * + * 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, float originX, float originY, boolean exact) { + // 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 = {originX, originY}; + // 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 (!exact) { + // 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; + touchXy[1] = originY; + + // 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; + } + + // This is used to compute the visual center of the dragView. This point is then + // used to visualize drop locations and determine where to drop an item. The idea is that + // the visual center represents the user's interpretation of where the item is, and hence + // is the appropriate point to use when determining drop location. + private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, + DragView dragView, float[] recycle) { + float res[]; + if (recycle == null) { + res = new float[2]; + } else { + res = recycle; + } + + // First off, the drag view has been shifted in a way that is not represented in the + // x and y values or the x/yOffsets. Here we account for that shift. + x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX); + y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); + + // These represent the visual top and left of drag view if a dragRect was provided. + // If a dragRect was not provided, then they correspond to the actual view left and + // top, as the dragRect is in that case taken to be the entire dragView. + // R.dimen.dragViewOffsetY. + int left = x - xOffset; + int top = y - yOffset; + + // In order to find the visual center, we shift by half the dragRect + res[0] = left + dragView.getDragRegion().width() / 2; + res[1] = top + dragView.getDragRegion().height() / 2; + + return res; + } + + private boolean isDragWidget(DragObject d) { + return (d.dragInfo instanceof LauncherAppWidgetInfo || + d.dragInfo instanceof PendingAddWidgetInfo); + } + private boolean isExternalDragWidget(DragObject d) { + return d.dragSource != this && isDragWidget(d); + } + + public void onDragOver(DragObject d) { + // Skip drag over events while we are dragging over side pages + if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return; + + Rect r = new Rect(); + CellLayout layout = null; + ItemInfo item = (ItemInfo) d.dragInfo; + + // Ensure that we have proper spans for the item that we are dropping + if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); + mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, + d.dragView, mDragViewVisualCenter); + + final View child = (mDragInfo == null) ? null : mDragInfo.cell; + // Identify whether we have dragged over a side page + if (isSmall()) { + if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { + mLauncher.getHotseat().getHitRect(r); + if (r.contains(d.x, d.y)) { + layout = mLauncher.getHotseat().getLayout(); + } + } + if (layout == null) { + layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false); + } + if (layout != mDragTargetLayout) { + + setCurrentDropLayout(layout); + setCurrentDragOverlappingLayout(layout); + + boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); + if (isInSpringLoadedMode) { + if (mLauncher.isHotseatLayout(layout)) { + mSpringLoadedDragController.cancel(); + } else { + mSpringLoadedDragController.setAlarm(mDragTargetLayout); + } + } + } + } else { + // Test to see if we are over the hotseat otherwise just use the current page + if (mLauncher.getHotseat() != null && !isDragWidget(d)) { + mLauncher.getHotseat().getHitRect(r); + if (r.contains(d.x, d.y)) { + layout = mLauncher.getHotseat().getLayout(); + } + } + if (layout == null) { + layout = getCurrentDropLayout(); + } + if (layout != mDragTargetLayout) { + setCurrentDropLayout(layout); + setCurrentDragOverlappingLayout(layout); + } + } + + // Handle the drag over + if (mDragTargetLayout != null) { + // We want the point to be mapped to the dragTarget. + if (mLauncher.isHotseatLayout(mDragTargetLayout)) { + mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); + } else { + mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); + } + + ItemInfo info = (ItemInfo) d.dragInfo; + + mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], item.spanX, item.spanY, + mDragTargetLayout, mTargetCell); + + setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]); + + float targetCellDistance = mDragTargetLayout.getDistanceFromCell( + mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); + + final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], + mTargetCell[1]); + + manageFolderFeedback(info, mDragTargetLayout, mTargetCell, + targetCellDistance, dragOverView); + + int minSpanX = item.spanX; + int minSpanY = item.spanY; + if (item.minSpanX > 0 && item.minSpanY > 0) { + minSpanX = item.minSpanX; + minSpanY = item.minSpanY; + } + + boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) + mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, + item.spanY, child, mTargetCell); + + if (!nearestDropOccupied) { + mDragTargetLayout.visualizeDropLocation(child, mDragOutline, + (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], + mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, + d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion()); + } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER) + && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] || + mLastReorderY != mTargetCell[1])) { + + // Otherwise, if we aren't adding to or creating a folder and there's no pending + // reorder, then we schedule a reorder + ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, + minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child); + mReorderAlarm.setOnAlarmListener(listener); + mReorderAlarm.setAlarm(REORDER_TIMEOUT); + } + + if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER || + !nearestDropOccupied) { + if (mDragTargetLayout != null) { + mDragTargetLayout.revertTempState(); + } + } + } + } + + private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout, + int[] targetCell, float distance, View dragOverView) { + boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance, + false); + + if (mDragMode == DRAG_MODE_NONE && userFolderPending && + !mFolderCreationAlarm.alarmPending()) { + mFolderCreationAlarm.setOnAlarmListener(new + FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1])); + mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); + return; + } + + boolean willAddToFolder = + willAddToExistingUserFolder(info, targetLayout, targetCell, distance); + + if (willAddToFolder && mDragMode == DRAG_MODE_NONE) { + mDragOverFolderIcon = ((FolderIcon) dragOverView); + mDragOverFolderIcon.onDragEnter(info); + if (targetLayout != null) { + targetLayout.clearDragOutlines(); + } + setDragMode(DRAG_MODE_ADD_TO_FOLDER); + return; + } + + if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) { + setDragMode(DRAG_MODE_NONE); + } + if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) { + setDragMode(DRAG_MODE_NONE); + } + + return; + } + + class FolderCreationAlarmListener implements OnAlarmListener { + CellLayout layout; + int cellX; + int cellY; + + public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { + this.layout = layout; + this.cellX = cellX; + this.cellY = cellY; + } + + public void onAlarm(Alarm alarm) { + if (mDragFolderRingAnimator == null) { + mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); + } + mDragFolderRingAnimator.setCell(cellX, cellY); + mDragFolderRingAnimator.setCellLayout(layout); + mDragFolderRingAnimator.animateToAcceptState(); + layout.showFolderAccept(mDragFolderRingAnimator); + layout.clearDragOutlines(); + setDragMode(DRAG_MODE_CREATE_FOLDER); + } + } + + class ReorderAlarmListener implements OnAlarmListener { + float[] dragViewCenter; + int minSpanX, minSpanY, spanX, spanY; + DragView dragView; + View child; + + public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, + int spanY, DragView dragView, View child) { + this.dragViewCenter = dragViewCenter; + this.minSpanX = minSpanX; + this.minSpanY = minSpanY; + this.spanX = spanX; + this.spanY = spanY; + this.child = child; + this.dragView = dragView; + } + + public void onAlarm(Alarm alarm) { + int[] resultSpan = new int[2]; + mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell); + mLastReorderX = mTargetCell[0]; + mLastReorderY = mTargetCell[1]; + + mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, + child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); + + if (mTargetCell[0] < 0 || mTargetCell[1] < 0) { + mDragTargetLayout.revertTempState(); + } else { + setDragMode(DRAG_MODE_REORDER); + } + + boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY; + mDragTargetLayout.visualizeDropLocation(child, mDragOutline, + (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], + mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, + dragView.getDragVisualizeOffset(), dragView.getDragRegion()); + } + } + + @Override + public void getHitRect(Rect outRect) { + // We want the workspace to have the whole area of the display (it will find the correct + // cell layout to drop to in the existing drag/drop logic. + outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); + } + + /** + * Add the item specified by dragInfo to the given layout. + * @return true if successful + */ + public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { + if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { + onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); + return true; + } + mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); + return false; + } + + private void onDropExternal(int[] touchXY, Object dragInfo, + CellLayout cellLayout, boolean insertAtFirst) { + onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); + } + + /** + * Drop an item that didn't originate on one of the workspace screens. + * It may have come from Launcher (e.g. from all apps or customize), or it may have + * come from another app altogether. + * + * 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(final int[] touchXY, final Object dragInfo, + final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { + final Runnable exitSpringLoadedRunnable = new Runnable() { + @Override + public void run() { + mLauncher.exitSpringLoadedDragModeDelayed(true, false, null); + } + }; + + ItemInfo info = (ItemInfo) dragInfo; + int spanX = info.spanX; + int spanY = info.spanY; + if (mDragInfo != null) { + spanX = mDragInfo.spanX; + spanY = mDragInfo.spanY; + } + + final long container = mLauncher.isHotseatLayout(cellLayout) ? + LauncherSettings.Favorites.CONTAINER_HOTSEAT : + LauncherSettings.Favorites.CONTAINER_DESKTOP; + final int screen = indexOfChild(cellLayout); + if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage + && mState != State.SPRING_LOADED) { + snapToPage(screen); + } + + if (info instanceof PendingAddItemInfo) { + final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; + + boolean findNearestVacantCell = true; + if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { + mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, + cellLayout, mTargetCell); + float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], + mDragViewVisualCenter[1], mTargetCell); + if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, + distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, + cellLayout, mTargetCell, distance)) { + findNearestVacantCell = false; + } + } + + final ItemInfo item = (ItemInfo) d.dragInfo; + if (findNearestVacantCell) { + int minSpanX = item.spanX; + int minSpanY = item.spanY; + if (item.minSpanX > 0 && item.minSpanY > 0) { + minSpanX = item.minSpanX; + minSpanY = item.minSpanY; + } + int[] resultSpan = new int[2]; + mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, + null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); + item.spanX = resultSpan[0]; + item.spanY = resultSpan[1]; + } + + Runnable onAnimationCompleteRunnable = new Runnable() { + @Override + public void run() { + // When dragging and dropping from customization tray, we deal with creating + // widgets/shortcuts/folders in a slightly different way + switch (pendingInfo.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + int span[] = new int[2]; + span[0] = item.spanX; + span[1] = item.spanY; + mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo, + container, screen, mTargetCell, span, null); + break; + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + mLauncher.processShortcutFromDrop(pendingInfo.componentName, + container, screen, mTargetCell, null); + break; + default: + throw new IllegalStateException("Unknown item type: " + + pendingInfo.itemType); + } + } + }; + View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET + ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null; + int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR; + if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && + ((PendingAddWidgetInfo) pendingInfo).info.configure != null) { + animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN; + } + animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, + animationStyle, finalView, true); + } else { + // This is for other drag/drop cases, like dragging from All Apps + 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); + } + view = mLauncher.createShortcut(R.layout.application, cellLayout, + (ShortcutInfo) info); + break; + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, + (FolderInfo) info, mIconCache); + break; + default: + throw new IllegalStateException("Unknown item type: " + info.itemType); + } + + // First we find the cell nearest to point at which the item is + // dropped, without any consideration to whether there is an item there. + if (touchXY != null) { + mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, + cellLayout, mTargetCell); + float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], + mDragViewVisualCenter[1], mTargetCell); + d.postAnimationRunnable = exitSpringLoadedRunnable; + if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, + true, d.dragView, d.postAnimationRunnable)) { + return; + } + if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, + true)) { + return; + } + } + + if (touchXY != null) { + // when dragging and dropping, just find the closest free spot + mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], 1, 1, 1, 1, + null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); + } else { + cellLayout.findCellForSpan(mTargetCell, 1, 1); + } + addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX, + info.spanY, insertAtFirst); + cellLayout.onDropChild(view); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); + cellLayout.getShortcutsAndWidgets().measureChild(view); + + + LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, + lp.cellX, lp.cellY); + + if (d.dragView != null) { + // We wrap the animation call in the temporary set and reset of the current + // cellLayout to its final transform -- this means we animate the drag view to + // the correct final location. + setFinalTransitionTransform(cellLayout); + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, + exitSpringLoadedRunnable); + resetTransitionTransform(cellLayout); + } + } + } + + public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, + widgetInfo.spanY, widgetInfo, false); + int visibility = layout.getVisibility(); + layout.setVisibility(VISIBLE); + + int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY); + int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY); + Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + + layout.measure(width, height); + layout.layout(0, 0, unScaledSize[0], unScaledSize[1]); + layout.draw(c); + c.setBitmap(null); + layout.setVisibility(visibility); + return b; + } + + private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY, + DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, + boolean external, boolean scale) { + // Now we animate the dragView, (ie. the widget or shortcut preview) into its final + // location and size on the home screen. + int spanX = info.spanX; + int spanY = info.spanY; + + Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY); + loc[0] = r.left; + loc[1] = r.top; + + setFinalTransitionTransform(layout); + float cellLayoutScale = + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc); + resetTransitionTransform(layout); + + float dragViewScaleX; + float dragViewScaleY; + if (scale) { + dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth(); + dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight(); + } else { + dragViewScaleX = 1f; + dragViewScaleY = 1f; + } + + // The animation will scale the dragView about its center, so we need to center about + // the final location. + loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2; + loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; + + scaleXY[0] = dragViewScaleX * cellLayoutScale; + scaleXY[1] = dragViewScaleY * cellLayoutScale; + } + + public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView, + final Runnable onCompleteRunnable, int animationType, final View finalView, + boolean external) { + Rect from = new Rect(); + mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from); + + int[] finalPos = new int[2]; + float scaleXY[] = new float[2]; + boolean scalePreview = !(info instanceof PendingAddShortcutInfo); + getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell, + external, scalePreview); + + Resources res = mLauncher.getResources(); + int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200; + + // In the case where we've prebound the widget, we remove it from the DragLayer + if (finalView instanceof AppWidgetHostView && external) { + Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView"); + mLauncher.getDragLayer().removeView(finalView); + } + if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) { + Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView); + dragView.setCrossFadeBitmap(crossFadeBitmap); + dragView.crossFade((int) (duration * 0.8f)); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) { + scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]); + } + + DragLayer dragLayer = mLauncher.getDragLayer(); + if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) { + mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f, + DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration); + } else { + int endStyle; + if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) { + endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE; + } else { + endStyle = DragLayer.ANIMATION_END_DISAPPEAR;; + } + + Runnable onComplete = new Runnable() { + @Override + public void run() { + if (finalView != null) { + finalView.setVisibility(VISIBLE); + } + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + } + }; + dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0], + finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle, + duration, this); + } + } + + public void setFinalTransitionTransform(CellLayout layout) { + if (isSwitchingState()) { + int index = indexOfChild(layout); + mCurrentScaleX = layout.getScaleX(); + mCurrentScaleY = layout.getScaleY(); + mCurrentTranslationX = layout.getTranslationX(); + mCurrentTranslationY = layout.getTranslationY(); + mCurrentRotationY = layout.getRotationY(); + layout.setScaleX(mNewScaleXs[index]); + layout.setScaleY(mNewScaleYs[index]); + layout.setTranslationX(mNewTranslationXs[index]); + layout.setTranslationY(mNewTranslationYs[index]); + layout.setRotationY(mNewRotationYs[index]); + } + } + public void resetTransitionTransform(CellLayout layout) { + if (isSwitchingState()) { + mCurrentScaleX = layout.getScaleX(); + mCurrentScaleY = layout.getScaleY(); + mCurrentTranslationX = layout.getTranslationX(); + mCurrentTranslationY = layout.getTranslationY(); + mCurrentRotationY = layout.getRotationY(); + layout.setScaleX(mCurrentScaleX); + layout.setScaleY(mCurrentScaleY); + layout.setTranslationX(mCurrentTranslationX); + layout.setTranslationY(mCurrentTranslationY); + layout.setRotationY(mCurrentRotationY); + } + } + + /** + * Return the current {@link CellLayout}, correctly picking the destination + * screen while a scroll is in progress. + */ + public CellLayout getCurrentDropLayout() { + return (CellLayout) getChildAt(getNextPage()); + } + + /** + * 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 CellLayout.CellInfo getDragInfo() { + return mDragInfo; + } + + /** + * Calculate the nearest cell where the given object would be dropped. + * + * pixelX and pixelY should be in the coordinate system of layout + */ + private int[] findNearestArea(int pixelX, int pixelY, + int spanX, int spanY, CellLayout layout, int[] recycle) { + return layout.findNearestArea( + pixelX, pixelY, spanX, spanY, recycle); + } + + void setup(DragController dragController) { + mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); + mDragController = dragController; + + // hardware layers on children are enabled on startup, but should be disabled until + // needed + updateChildrenLayersEnabled(); + setWallpaperDimension(); + } + + /** + * Called at the end of a drag which originated on the workspace. + */ + public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, + boolean success) { + if (success) { + if (target != this) { + if (mDragInfo != null) { + getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); + if (mDragInfo.cell instanceof DropTarget) { + mDragController.removeDropTarget((DropTarget) mDragInfo.cell); + } + } + } + } else if (mDragInfo != null) { + CellLayout cellLayout; + if (mLauncher.isHotseatLayout(target)) { + cellLayout = mLauncher.getHotseat().getLayout(); + } else { + cellLayout = (CellLayout) getChildAt(mDragInfo.screen); + } + cellLayout.onDropChild(mDragInfo.cell); + } + if (d.cancelled && mDragInfo.cell != null) { + mDragInfo.cell.setVisibility(VISIBLE); + } + mDragOutline = null; + mDragInfo = null; + + // Hide the scrolling indicator after you pick up an item + hideScrollingIndicator(false); + } + + void updateItemLocationsInDatabase(CellLayout cl) { + int count = cl.getShortcutsAndWidgets().getChildCount(); + + int screen = indexOfChild(cl); + int container = Favorites.CONTAINER_DESKTOP; + + if (mLauncher.isHotseatLayout(cl)) { + screen = -1; + container = Favorites.CONTAINER_HOTSEAT; + } + + for (int i = 0; i < count; i++) { + View v = cl.getShortcutsAndWidgets().getChildAt(i); + ItemInfo info = (ItemInfo) v.getTag(); + // Null check required as the AllApps button doesn't have an item info + if (info != null) { + LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX, + info.cellY, info.spanX, info.spanY); + } + } + } + + @Override + public boolean supportsFlingToDelete() { + return true; + } + + @Override + public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { + // Do nothing + } + + @Override + public void onFlingToDeleteCompleted() { + // Do nothing + } + + public boolean isDropEnabled() { + return true; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(state); + Launcher.setScreen(mCurrentPage); + } + + @Override + public void scrollLeft() { + if (!isSmall() && !mIsSwitchingState) { + super.scrollLeft(); + } + Folder openFolder = getOpenFolder(); + if (openFolder != null) { + openFolder.completeDragExit(); + } + } + + @Override + public void scrollRight() { + if (!isSmall() && !mIsSwitchingState) { + super.scrollRight(); + } + Folder openFolder = getOpenFolder(); + if (openFolder != null) { + openFolder.completeDragExit(); + } + } + + @Override + public boolean onEnterScrollArea(int x, int y, int direction) { + // Ignore the scroll area if we are dragging over the hot seat + boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext()); + if (mLauncher.getHotseat() != null && isPortrait) { + Rect r = new Rect(); + mLauncher.getHotseat().getHitRect(r); + if (r.contains(x, y)) { + return false; + } + } + + boolean result = false; + if (!isSmall() && !mIsSwitchingState) { + mInScrollArea = true; + + final int page = getNextPage() + + (direction == DragController.SCROLL_LEFT ? -1 : 1); + + // We always want to exit the current layout to ensure parity of enter / exit + setCurrentDropLayout(null); + + if (0 <= page && page < getChildCount()) { + CellLayout layout = (CellLayout) getChildAt(page); + setCurrentDragOverlappingLayout(layout); + + // Workspace is responsible for drawing the edge glow on adjacent pages, + // so we need to redraw the workspace when this may have changed. + invalidate(); + result = true; + } + } + return result; + } + + @Override + public boolean onExitScrollArea() { + boolean result = false; + if (mInScrollArea) { + invalidate(); + CellLayout layout = getCurrentDropLayout(); + setCurrentDropLayout(layout); + setCurrentDragOverlappingLayout(layout); + + result = true; + mInScrollArea = false; + } + return result; + } + + private void onResetScrollArea() { + setCurrentDragOverlappingLayout(null); + mInScrollArea = false; + } + + /** + * Returns a specific CellLayout + */ + CellLayout getParentCellLayoutForView(View v) { + ArrayList layouts = getWorkspaceAndHotseatCellLayouts(); + for (CellLayout layout : layouts) { + if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) { + return layout; + } + } + return null; + } + + /** + * Returns a list of all the CellLayouts in the workspace. + */ + ArrayList getWorkspaceAndHotseatCellLayouts() { + ArrayList layouts = new ArrayList(); + int screenCount = getChildCount(); + for (int screen = 0; screen < screenCount; screen++) { + layouts.add(((CellLayout) getChildAt(screen))); + } + if (mLauncher.getHotseat() != null) { + layouts.add(mLauncher.getHotseat().getLayout()); + } + return layouts; + } + + /** + * We should only use this to search for specific children. Do not use this method to modify + * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from + * the hotseat and workspace pages + */ + ArrayList getAllShortcutAndWidgetContainers() { + ArrayList childrenLayouts = + new ArrayList(); + int screenCount = getChildCount(); + for (int screen = 0; screen < screenCount; screen++) { + childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); + } + if (mLauncher.getHotseat() != null) { + childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets()); + } + return childrenLayouts; + } + + public Folder getFolderForTag(Object tag) { + ArrayList childrenLayouts = + getAllShortcutAndWidgetContainers(); + for (ShortcutAndWidgetContainer layout: childrenLayouts) { + int count = layout.getChildCount(); + for (int i = 0; i < count; i++) { + View child = layout.getChildAt(i); + if (child instanceof Folder) { + Folder f = (Folder) child; + if (f.getInfo() == tag && f.getInfo().opened) { + return f; + } + } + } + } + return null; + } + + public View getViewForTag(Object tag) { + ArrayList childrenLayouts = + getAllShortcutAndWidgetContainers(); + for (ShortcutAndWidgetContainer layout: childrenLayouts) { + int count = layout.getChildCount(); + for (int i = 0; i < count; i++) { + View child = layout.getChildAt(i); + if (child.getTag() == tag) { + return child; + } + } + } + return null; + } + + void clearDropTargets() { + ArrayList childrenLayouts = + getAllShortcutAndWidgetContainers(); + for (ShortcutAndWidgetContainer layout: childrenLayouts) { + int childCount = layout.getChildCount(); + for (int j = 0; j < childCount; j++) { + View v = layout.getChildAt(j); + if (v instanceof DropTarget) { + mDragController.removeDropTarget((DropTarget) v); + } + } + } + } + + void removeItems(final ArrayList apps) { + final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); + + final HashSet packageNames = new HashSet(); + final int appCount = apps.size(); + for (int i = 0; i < appCount; i++) { + packageNames.add(apps.get(i).componentName.getPackageName()); + } + + ArrayList cellLayouts = getWorkspaceAndHotseatCellLayouts(); + for (final CellLayout layoutParent: cellLayouts) { + final ViewGroup layout = layoutParent.getShortcutsAndWidgets(); + + // Avoid ANRs by treating each screen separately + post(new Runnable() { + public void run() { + final ArrayList childrenToRemove = new ArrayList(); + 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 (name != null) { + if (packageNames.contains(name.getPackageName())) { + LauncherModel.deleteItemFromDatabase(mLauncher, info); + childrenToRemove.add(view); + } + } + } else if (tag instanceof FolderInfo) { + final FolderInfo info = (FolderInfo) tag; + final ArrayList contents = info.contents; + final int contentsCount = contents.size(); + final ArrayList appsToRemoveFromFolder = + new ArrayList(); + + for (int k = 0; k < contentsCount; k++) { + final ShortcutInfo appInfo = contents.get(k); + final Intent intent = appInfo.intent; + final ComponentName name = intent.getComponent(); + + if (name != null) { + if (packageNames.contains(name.getPackageName())) { + appsToRemoveFromFolder.add(appInfo); + } + } + } + for (ShortcutInfo item: appsToRemoveFromFolder) { + info.remove(item); + LauncherModel.deleteItemFromDatabase(mLauncher, item); + } + } else if (tag instanceof LauncherAppWidgetInfo) { + final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; + final ComponentName provider = info.providerName; + if (provider != null) { + if (packageNames.contains(provider.getPackageName())) { + LauncherModel.deleteItemFromDatabase(mLauncher, info); + childrenToRemove.add(view); + } + } + } + } + + childCount = childrenToRemove.size(); + for (int j = 0; j < childCount; j++) { + View child = childrenToRemove.get(j); + // Note: We can not remove the view directly from CellLayoutChildren as this + // does not re-mark the spaces as unoccupied. + layoutParent.removeViewInLayout(child); + if (child instanceof DropTarget) { + mDragController.removeDropTarget((DropTarget)child); + } + } + + if (childCount > 0) { + layout.requestLayout(); + layout.invalidate(); + } + } + }); + } + + // It is no longer the case the BubbleTextViews correspond 1:1 with the workspace items in + // the database (and LauncherModel) since shortcuts are not added and animated in until + // the user returns to launcher. As a result, we really should be cleaning up the Db + // regardless of whether the item was added or not (unlike the logic above). This is only + // relevant for direct workspace items. + post(new Runnable() { + @Override + public void run() { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sp = getContext().getSharedPreferences(spKey, + Context.MODE_PRIVATE); + Set newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, + null); + + for (String packageName: packageNames) { + // Remove all items that have the same package, but were not removed above + ArrayList infos = + mLauncher.getModel().getShortcutInfosForPackage(packageName); + for (ShortcutInfo info : infos) { + LauncherModel.deleteItemFromDatabase(mLauncher, info); + } + // Remove all queued items that match the same package + if (newApps != null) { + synchronized (newApps) { + Iterator iter = newApps.iterator(); + while (iter.hasNext()) { + try { + Intent intent = Intent.parseUri(iter.next(), 0); + String pn = ItemInfo.getPackageName(intent); + if (packageNames.contains(pn)) { + iter.remove(); + } + } catch (URISyntaxException e) {} + } + } + } + } + } + }); + } + + void updateShortcuts(ArrayList apps) { + ArrayList childrenLayouts = getAllShortcutAndWidgetContainers(); + for (ShortcutAndWidgetContainer layout: childrenLayouts) { + 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) { + ShortcutInfo info = (ShortcutInfo) tag; + // We need to check for ACTION_MAIN otherwise getComponent() might + // return null for some shortcuts (for instance, for shortcuts to + // web pages.) + final Intent intent = info.intent; + final ComponentName name = intent.getComponent(); + if (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++) { + ApplicationInfo app = apps.get(k); + if (app.componentName.equals(name)) { + BubbleTextView shortcut = (BubbleTextView) view; + info.updateIcon(mIconCache); + info.title = app.title.toString(); + shortcut.applyFromShortcutInfo(info, mIconCache); + } + } + } + } + } + } + } + + void moveToDefaultScreen(boolean animate) { + if (!isSmall()) { + if (animate) { + snapToPage(mDefaultPage); + } else { + setCurrentPage(mDefaultPage); + } + } + getChildAt(mDefaultPage).requestFocus(); + } + + @Override + public void syncPages() { + } + + @Override + public void syncPageItems(int page, boolean immediate) { + } + + @Override + protected String getCurrentPageDescription() { + int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + return String.format(getContext().getString(R.string.workspace_scroll_format), + page + 1, getChildCount()); + } + + public void getLocationInDragLayer(int[] loc) { + mLauncher.getDragLayer().getLocationInDragLayer(this, loc); + } + + void setFadeForOverScroll(float fade) { + if (!isScrollingIndicatorEnabled()) return; + + mOverscrollFade = fade; + float reducedFade = 0.5f + 0.5f * (1 - fade); + final ViewGroup parent = (ViewGroup) getParent(); + final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider)); + final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider)); + final View scrollIndicator = getScrollingIndicator(); + + cancelScrollingIndicatorAnimations(); + if (qsbDivider != null) qsbDivider.setAlpha(reducedFade); + if (dockDivider != null) dockDivider.setAlpha(reducedFade); + scrollIndicator.setAlpha(1 - fade); + } +} diff --git a/tests/stress/Android.mk b/tests/stress/Android.mk index 68289bd3e..4678f677d 100644 --- a/tests/stress/Android.mk +++ b/tests/stress/Android.mk @@ -22,10 +22,10 @@ LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_PACKAGE_NAME := LauncherRotationStressTest +LOCAL_PACKAGE_NAME := TrebuchetRotationStressTest LOCAL_CERTIFICATE := shared -LOCAL_INSTRUMENTATION_FOR := Launcher2 +LOCAL_INSTRUMENTATION_FOR := Trebuchet include $(BUILD_PACKAGE) diff --git a/tests/stress/AndroidManifest.xml b/tests/stress/AndroidManifest.xml index 0df3a9da3..71c9e7afd 100644 --- a/tests/stress/AndroidManifest.xml +++ b/tests/stress/AndroidManifest.xml @@ -24,6 +24,6 @@ + android:label="Rotation stress test using Trebuchet"> diff --git a/tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java b/tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java deleted file mode 100644 index d21fd53ba..000000000 --- a/tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher2.stress; - - -import com.android.launcher2.Launcher; - -import android.content.pm.ActivityInfo; -import android.os.SystemClock; -import android.test.ActivityInstrumentationTestCase2; -import android.test.RepetitiveTest; -import android.util.Log; - -/** - * Run rotation stress test using Launcher2 for 50 iterations. - */ -public class LauncherRotationStressTest extends ActivityInstrumentationTestCase2 { - - private static final int NUM_ITERATIONS = 50; - private static final int WAIT_TIME_MS = 500; - private static final String LOG_TAG = "LauncherRotationStressTest"; - - public LauncherRotationStressTest() { - super(Launcher.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - @RepetitiveTest(numIterations=NUM_ITERATIONS) - public void testLauncherRotationStress() throws Exception { - Launcher launcher = getActivity(); - getInstrumentation().waitForIdleSync(); - SystemClock.sleep(WAIT_TIME_MS); - launcher.setRequestedOrientation( - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - getInstrumentation().waitForIdleSync(); - SystemClock.sleep(WAIT_TIME_MS); - launcher.setRequestedOrientation( - ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } -} diff --git a/tests/stress/src/com/cyanogenmod/trebuchet/stress/LauncherRotationStressTest.java b/tests/stress/src/com/cyanogenmod/trebuchet/stress/LauncherRotationStressTest.java new file mode 100644 index 000000000..e2ced63a6 --- /dev/null +++ b/tests/stress/src/com/cyanogenmod/trebuchet/stress/LauncherRotationStressTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.trebuchet.stress; + + +import com.cyanogenmod.trebuchet.Launcher; + +import android.content.pm.ActivityInfo; +import android.os.SystemClock; +import android.test.ActivityInstrumentationTestCase2; +import android.test.RepetitiveTest; +import android.util.Log; + +/** + * Run rotation stress test using Trebuchet for 50 iterations. + */ +public class LauncherRotationStressTest extends ActivityInstrumentationTestCase2 { + + private static final int NUM_ITERATIONS = 50; + private static final int WAIT_TIME_MS = 500; + private static final String LOG_TAG = "TrebuchetRotationStressTest"; + + public LauncherRotationStressTest() { + super(Launcher.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + @RepetitiveTest(numIterations=NUM_ITERATIONS) + public void testLauncherRotationStress() throws Exception { + Launcher launcher = getActivity(); + getInstrumentation().waitForIdleSync(); + SystemClock.sleep(WAIT_TIME_MS); + launcher.setRequestedOrientation( + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + getInstrumentation().waitForIdleSync(); + SystemClock.sleep(WAIT_TIME_MS); + launcher.setRequestedOrientation( + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } +} -- cgit v1.2.3