summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Android.mk5
-rw-r--r--AndroidManifest.xml35
-rw-r--r--WallpaperPicker/AndroidManifest.xml4
-rw-r--r--WallpaperPicker/res/layout/wallpaper_cropper.xml3
-rw-r--r--WallpaperPicker/res/layout/wallpaper_picker.xml42
-rw-r--r--WallpaperPicker/res/layout/wallpaper_picker_image_picker_item.xml1
-rw-r--r--WallpaperPicker/res/layout/wallpaper_picker_item.xml1
-rw-r--r--WallpaperPicker/res/layout/wallpaper_picker_live_wallpaper_item.xml1
-rw-r--r--WallpaperPicker/res/layout/wallpaper_picker_third_party_item.xml1
-rw-r--r--WallpaperPicker/res/values-sw720dp-v19/styles.xml2
-rw-r--r--WallpaperPicker/res/values-sw720dp/styles.xml2
-rw-r--r--WallpaperPicker/src/android/util/Pools.java165
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java405
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java253
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/common/Utils.java271
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java1
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java3
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/glrenderer/IntArray.java (renamed from WallpaperPicker/src/com/android/gallery3d/util/IntArray.java)2
-rw-r--r--WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java4
-rw-r--r--WallpaperPicker/src/com/android/launcher3/CropView.java5
-rw-r--r--WallpaperPicker/src/com/android/launcher3/LauncherWallpaperPickerActivity.java (renamed from src/com/android/launcher3/LauncherWallpaperPickerActivity.java)13
-rw-r--r--WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java15
-rw-r--r--WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java5
-rw-r--r--WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java11
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java790
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java353
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java39
-rw-r--r--WallpaperPicker/src/com/android/launcher3/base/BaseActivity.java21
-rw-r--r--WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java147
-rw-r--r--WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java438
-rw-r--r--WallpaperPicker/src/com/android/photos/views/TiledImageRenderer.java27
-rw-r--r--WallpaperPicker/src/com/android/photos/views/TiledImageView.java124
-rw-r--r--proguard.flags6
-rw-r--r--protos/backup.proto12
-rw-r--r--res/drawable-hdpi/ic_launcher_market_holo.pngbin1644 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_pageindicator_current.pngbin1038 -> 1038 bytes
-rw-r--r--res/drawable-hdpi/ic_pageindicator_current_folder.pngbin0 -> 610 bytes
-rw-r--r--res/drawable-hdpi/ic_pageindicator_default.pngbin895 -> 895 bytes
-rw-r--r--res/drawable-hdpi/ic_pageindicator_default_folder.pngbin0 -> 529 bytes
-rw-r--r--res/drawable-ldrtl/apps_list_fastscroll_bg.xml27
-rw-r--r--res/drawable-mdpi/ic_launcher_market_holo.pngbin1124 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_pageindicator_current.pngbin704 -> 704 bytes
-rw-r--r--res/drawable-mdpi/ic_pageindicator_current_folder.pngbin0 -> 484 bytes
-rw-r--r--res/drawable-mdpi/ic_pageindicator_default.pngbin694 -> 694 bytes
-rw-r--r--res/drawable-mdpi/ic_pageindicator_default_folder.pngbin0 -> 482 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher_market_holo.pngbin2187 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_pageindicator_current.pngbin1414 -> 1414 bytes
-rw-r--r--res/drawable-xhdpi/ic_pageindicator_current_folder.pngbin0 -> 782 bytes
-rw-r--r--res/drawable-xhdpi/ic_pageindicator_default.pngbin1168 -> 1168 bytes
-rw-r--r--res/drawable-xhdpi/ic_pageindicator_default_folder.pngbin0 -> 644 bytes
-rw-r--r--res/drawable-xxhdpi/ic_pageindicator_current.pngbin2105 -> 2105 bytes
-rw-r--r--res/drawable-xxhdpi/ic_pageindicator_current_folder.pngbin0 -> 1023 bytes
-rw-r--r--res/drawable-xxhdpi/ic_pageindicator_default.pngbin1634 -> 1634 bytes
-rw-r--r--res/drawable-xxhdpi/ic_pageindicator_default_folder.pngbin0 -> 832 bytes
-rw-r--r--res/drawable/apps_list_bg.xml22
-rw-r--r--res/drawable/apps_list_fastscroll_bg.xml27
-rw-r--r--res/drawable/apps_list_scrollbar_thumb.xml21
-rw-r--r--res/drawable/apps_list_search_bg.xml23
-rw-r--r--res/drawable/apps_reveal_bg.xml21
-rw-r--r--res/drawable/apps_search_bg.xml23
-rw-r--r--res/layout-land/launcher.xml10
-rw-r--r--res/layout-port/launcher.xml10
-rw-r--r--res/layout-sw600dp/apps_view.xml33
-rw-r--r--res/layout-sw720dp/launcher.xml10
-rw-r--r--res/layout/apps_customize_pane.xml62
-rw-r--r--res/layout/apps_empty_view.xml28
-rw-r--r--res/layout/apps_grid_row_icon_view.xml29
-rw-r--r--res/layout/apps_list_row_icon_view.xml29
-rw-r--r--res/layout/apps_list_row_view.xml33
-rw-r--r--res/layout/apps_list_view.xml51
-rw-r--r--res/layout/apps_reveal_view.xml24
-rw-r--r--res/layout/apps_view.xml35
-rw-r--r--res/layout/page_indicator_marker.xml4
-rw-r--r--res/layout/search_drop_target_bar.xml13
-rw-r--r--res/layout/user_folder.xml88
-rw-r--r--res/layout/widget_cell.xml (renamed from res/layout/apps_customize_widget.xml)69
-rw-r--r--res/layout/widgets_list_row_view.xml91
-rw-r--r--res/layout/widgets_view.xml49
-rw-r--r--res/values-be/strings.xml4
-rw-r--r--res/values-et/strings.xml20
-rw-r--r--res/values-land/dimens.xml5
-rw-r--r--res/values-ms/strings.xml20
-rw-r--r--res/values-sw600dp/dimens.xml14
-rw-r--r--res/values-sw720dp/dimens.xml5
-rw-r--r--res/values-v17/styles.xml4
-rw-r--r--res/values/attrs.xml15
-rw-r--r--res/values/colors.xml6
-rw-r--r--res/values/config.xml1
-rw-r--r--res/values/dimens.xml48
-rw-r--r--res/values/strings.xml49
-rw-r--r--res/values/styles.xml4
-rw-r--r--res/xml/app_target_browser.xml23
-rw-r--r--res/xml/app_target_camera.xml23
-rw-r--r--res/xml/app_target_email.xml23
-rw-r--r--res/xml/app_target_gallery.xml23
-rw-r--r--res/xml/app_target_messenger.xml26
-rw-r--r--res/xml/app_target_phone.xml24
-rw-r--r--src/com/android/launcher3/AllAppsList.java13
-rw-r--r--src/com/android/launcher3/AlphabeticalAppsList.java316
-rw-r--r--src/com/android/launcher3/AppInfo.java28
-rw-r--r--src/com/android/launcher3/AppWidgetsRestoredReceiver.java5
-rw-r--r--src/com/android/launcher3/AppsContainerRecyclerView.java412
-rw-r--r--src/com/android/launcher3/AppsContainerView.java549
-rw-r--r--src/com/android/launcher3/AppsCustomizePagedView.java1574
-rw-r--r--src/com/android/launcher3/AppsCustomizeTabHost.java220
-rw-r--r--src/com/android/launcher3/AppsGridAdapter.java258
-rw-r--r--src/com/android/launcher3/AppsListAdapter.java143
-rw-r--r--src/com/android/launcher3/AutoInstallsLayout.java63
-rw-r--r--src/com/android/launcher3/BubbleTextView.java163
-rw-r--r--src/com/android/launcher3/ButtonDropTarget.java132
-rw-r--r--src/com/android/launcher3/CellLayout.java364
-rw-r--r--src/com/android/launcher3/CheckLongPressHelper.java6
-rw-r--r--src/com/android/launcher3/CommonAppTypeParser.java152
-rw-r--r--src/com/android/launcher3/DefaultLayoutParser.java24
-rw-r--r--src/com/android/launcher3/DeferredHandler.java6
-rw-r--r--src/com/android/launcher3/DeleteDropTarget.java302
-rw-r--r--src/com/android/launcher3/DeviceProfile.java109
-rw-r--r--src/com/android/launcher3/DragController.java89
-rw-r--r--src/com/android/launcher3/DragLayer.java11
-rw-r--r--src/com/android/launcher3/DragSource.java2
-rw-r--r--src/com/android/launcher3/DragView.java16
-rw-r--r--src/com/android/launcher3/DropTarget.java30
-rw-r--r--src/com/android/launcher3/DynamicGrid.java15
-rw-r--r--src/com/android/launcher3/FastBitmapDrawable.java4
-rw-r--r--src/com/android/launcher3/FirstFrameAnimatorHelper.java4
-rw-r--r--src/com/android/launcher3/FocusHelper.java917
-rw-r--r--src/com/android/launcher3/FocusIndicatorView.java52
-rw-r--r--src/com/android/launcher3/FocusOnlyTabWidget.java86
-rw-r--r--src/com/android/launcher3/Folder.java777
-rw-r--r--src/com/android/launcher3/FolderAutoScrollHelper.java59
-rw-r--r--src/com/android/launcher3/FolderIcon.java21
-rw-r--r--src/com/android/launcher3/FolderInfo.java41
-rw-r--r--src/com/android/launcher3/FolderPagedView.java842
-rw-r--r--src/com/android/launcher3/Hotseat.java124
-rw-r--r--src/com/android/launcher3/IconCache.java616
-rw-r--r--src/com/android/launcher3/InfoDropTarget.java85
-rw-r--r--src/com/android/launcher3/Insettable.java4
-rw-r--r--src/com/android/launcher3/InstallShortcutReceiver.java65
-rw-r--r--src/com/android/launcher3/InterruptibleInOutAnimator.java4
-rw-r--r--src/com/android/launcher3/ItemInfo.java35
-rw-r--r--src/com/android/launcher3/Launcher.java1182
-rw-r--r--src/com/android/launcher3/LauncherAccessibilityDelegate.java115
-rw-r--r--src/com/android/launcher3/LauncherAppState.java74
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetHost.java6
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetProviderInfo.java17
-rw-r--r--src/com/android/launcher3/LauncherBackupAgentHelper.java2
-rw-r--r--src/com/android/launcher3/LauncherBackupHelper.java158
-rw-r--r--src/com/android/launcher3/LauncherCallbacks.java10
-rw-r--r--src/com/android/launcher3/LauncherClings.java8
-rw-r--r--src/com/android/launcher3/LauncherExtension.java14
-rw-r--r--src/com/android/launcher3/LauncherFiles.java18
-rw-r--r--src/com/android/launcher3/LauncherModel.java745
-rw-r--r--src/com/android/launcher3/LauncherProvider.java467
-rw-r--r--src/com/android/launcher3/LauncherScroller.java5
-rw-r--r--src/com/android/launcher3/LauncherSettings.java65
-rw-r--r--src/com/android/launcher3/LauncherStateTransitionAnimation.java783
-rw-r--r--src/com/android/launcher3/PackageChangedReceiver.java21
-rw-r--r--src/com/android/launcher3/PagedView.java73
-rw-r--r--src/com/android/launcher3/PagedViewCellLayout.java492
-rw-r--r--src/com/android/launcher3/PagedViewCellLayoutChildren.java161
-rw-r--r--src/com/android/launcher3/PagedViewWithDraggableItems.java174
-rw-r--r--src/com/android/launcher3/PendingAddItemInfo.java88
-rw-r--r--src/com/android/launcher3/SearchDropTargetBar.java39
-rw-r--r--src/com/android/launcher3/ShortcutAndWidgetContainer.java2
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java48
-rw-r--r--src/com/android/launcher3/Stats.java142
-rw-r--r--src/com/android/launcher3/UninstallDropTarget.java122
-rw-r--r--src/com/android/launcher3/UninstallShortcutReceiver.java133
-rw-r--r--src/com/android/launcher3/Utilities.java32
-rw-r--r--src/com/android/launcher3/WeightWatcher.java6
-rw-r--r--src/com/android/launcher3/WidgetPreviewLoader.java1057
-rw-r--r--src/com/android/launcher3/Workspace.java436
-rw-r--r--src/com/android/launcher3/compat/AlphabeticIndexCompat.java149
-rw-r--r--src/com/android/launcher3/compat/LauncherActivityInfoCompat.java9
-rw-r--r--src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java45
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatV16.java6
-rw-r--r--src/com/android/launcher3/compat/PackageInstallerCompatVL.java11
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompat.java1
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompatV16.java5
-rw-r--r--src/com/android/launcher3/compat/UserManagerCompatVL.java20
-rw-r--r--src/com/android/launcher3/util/FocusLogic.java508
-rw-r--r--src/com/android/launcher3/util/ManagedProfileHeuristic.java277
-rw-r--r--src/com/android/launcher3/util/Thunk.java43
-rw-r--r--src/com/android/launcher3/util/WallpaperUtils.java123
-rw-r--r--src/com/android/launcher3/widget/PackageItemInfo.java58
-rw-r--r--src/com/android/launcher3/widget/PendingAddShortcutInfo.java44
-rw-r--r--src/com/android/launcher3/widget/PendingAddWidgetInfo.java91
-rw-r--r--src/com/android/launcher3/widget/WidgetCell.java (renamed from src/com/android/launcher3/PagedViewWidget.java)161
-rw-r--r--src/com/android/launcher3/widget/WidgetImageView.java (renamed from src/com/android/launcher3/PagedViewWidgetImageView.java)7
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerView.java372
-rw-r--r--src/com/android/launcher3/widget/WidgetsListAdapter.java177
-rw-r--r--src/com/android/launcher3/widget/WidgetsModel.java128
-rw-r--r--src/com/android/launcher3/widget/WidgetsRowView.java (renamed from src/com/android/launcher3/PagedViewGridLayout.java)77
-rw-r--r--src/com/android/launcher3/widget/WidgetsRowViewHolder.java (renamed from src/com/android/launcher3/UserInitializeReceiver.java)30
-rw-r--r--tests/Android.mk27
-rw-r--r--tests/AndroidManifest.xml (renamed from tests/stress/AndroidManifest.xml)6
-rw-r--r--tests/res/values/string.xml21
-rw-r--r--tests/src/com/android/launcher3/util/FocusLogicTest.java60
-rw-r--r--tests/stress/Android.mk31
-rw-r--r--tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java63
201 files changed, 11961 insertions, 10401 deletions
diff --git a/.gitignore b/.gitignore
index 4d5667e54..aea5d6102 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@ db_files
*.iml
.project
.classpath
+.project.properties
gen/
tests/stress/gen/
WallpaperPicker/gen/
+WallpaperPicker/.project.properties
+bin/
diff --git a/Android.mk b/Android.mk
index 340caca0b..d7bfdd464 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,7 +23,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, WallpaperPicker/src) \
@@ -37,6 +37,9 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
LOCAL_SDK_VERSION := 21
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ android-support-v7-recyclerview
LOCAL_PACKAGE_NAME := Launcher3
#LOCAL_CERTIFICATE := shared
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fb7ac3fab..b61b90c5c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -29,12 +29,6 @@
android:label="@string/permlab_install_shortcut"
android:description="@string/permdesc_install_shortcut" />
<permission
- android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
- android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="dangerous"
- android:label="@string/permlab_uninstall_shortcut"
- android:description="@string/permdesc_uninstall_shortcut"/>
- <permission
android:name="com.android.launcher3.permission.READ_SETTINGS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
@@ -128,7 +122,7 @@
</activity>
<activity
- android:name="com.android.launcher3.LauncherWallpaperPickerActivity"
+ android:name="com.android.launcher3.WallpaperPickerActivity"
android:theme="@style/Theme.WallpaperPicker"
android:label="@string/pick_wallpaper"
android:icon="@mipmap/ic_launcher_wallpaper"
@@ -191,15 +185,6 @@
</intent-filter>
</receiver>
- <!-- Intent received used to uninstall shortcuts from other applications -->
- <receiver
- android:name="com.android.launcher3.UninstallShortcutReceiver"
- android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT">
- <intent-filter>
- <action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT" />
- </intent-filter>
- </receiver>
-
<!-- Intent received used to initialize a restored widget -->
<receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver" >
<intent-filter>
@@ -207,24 +192,6 @@
</intent-filter>
</receiver>
- <!-- New user initialization; set up initial wallpaper -->
- <receiver
- android:name="com.android.launcher3.UserInitializeReceiver"
- android:exported="false">
- <intent-filter>
- <action android:name="android.intent.action.USER_INITIALIZE" />
- </intent-filter>
- </receiver>
-
- <receiver android:name="com.android.launcher3.PackageChangedReceiver" >
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_CHANGED"/>
- <action android:name="android.intent.action.PACKAGE_REPLACED"/>
- <action android:name="android.intent.action.PACKAGE_REMOVED"/>
- <data android:scheme="package"></data>
- </intent-filter>
- </receiver>
-
<receiver android:name="com.android.launcher3.StartupReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/WallpaperPicker/AndroidManifest.xml b/WallpaperPicker/AndroidManifest.xml
index 5b6a0077d..cb1457bdc 100644
--- a/WallpaperPicker/AndroidManifest.xml
+++ b/WallpaperPicker/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:versionCode="1"
android:versionName="1.0"
>
-
- <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="19" />
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
<application/>
</manifest>
diff --git a/WallpaperPicker/res/layout/wallpaper_cropper.xml b/WallpaperPicker/res/layout/wallpaper_cropper.xml
index abb860898..ffe8df0fb 100644
--- a/WallpaperPicker/res/layout/wallpaper_cropper.xml
+++ b/WallpaperPicker/res/layout/wallpaper_cropper.xml
@@ -19,7 +19,6 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/wallpaper_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.launcher3.CropView
@@ -28,7 +27,7 @@
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/loading"
- style="@android:style/Widget.Holo.ProgressBar.Large"
+ style="?android:attr/progressBarStyleLarge"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/WallpaperPicker/res/layout/wallpaper_picker.xml b/WallpaperPicker/res/layout/wallpaper_picker.xml
index c36493d2f..0b970b09f 100644
--- a/WallpaperPicker/res/layout/wallpaper_picker.xml
+++ b/WallpaperPicker/res/layout/wallpaper_picker.xml
@@ -18,60 +18,74 @@
*/
-->
-<com.android.launcher3.WallpaperRootView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/wallpaper_root"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent" >
+
<com.android.launcher3.CropView
android:id="@+id/cropView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
+
<ProgressBar
android:id="@+id/loading"
- style="@android:style/Widget.Holo.ProgressBar.Large"
- android:visibility="invisible"
+ style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerInParent="true"
+ android:layout_gravity="center"
android:indeterminate="true"
android:indeterminateOnly="true"
- android:background="@android:color/transparent" />
+ android:visibility="invisible" />
+
<LinearLayout
android:id="@+id/wallpaper_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
+ android:layout_gravity="bottom"
+ android:fitsSystemWindows="true"
android:orientation="vertical" >
+
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@drawable/tile_shadow_top" />
+
<HorizontalScrollView
android:id="@+id/wallpaper_scroll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
- <LinearLayout android:id="@+id/master_wallpaper_list"
+
+ <LinearLayout
+ android:id="@+id/master_wallpaper_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
- <LinearLayout android:id="@+id/wallpaper_list"
+
+ <LinearLayout
+ android:id="@+id/wallpaper_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
- <LinearLayout android:id="@+id/live_wallpaper_list"
+
+ <LinearLayout
+ android:id="@+id/live_wallpaper_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
- <LinearLayout android:id="@+id/third_party_wallpaper_list"
+
+ <LinearLayout
+ android:id="@+id/third_party_wallpaper_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</LinearLayout>
</HorizontalScrollView>
+
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@drawable/tile_shadow_bottom" />
</LinearLayout>
-</com.android.launcher3.WallpaperRootView>
+
+</FrameLayout> \ No newline at end of file
diff --git a/WallpaperPicker/res/layout/wallpaper_picker_image_picker_item.xml b/WallpaperPicker/res/layout/wallpaper_picker_image_picker_item.xml
index ae3c43d8e..dc6524486 100644
--- a/WallpaperPicker/res/layout/wallpaper_picker_image_picker_item.xml
+++ b/WallpaperPicker/res/layout/wallpaper_picker_image_picker_item.xml
@@ -20,7 +20,6 @@
android:layout_height="@dimen/wallpaperThumbnailHeight"
android:focusable="true"
android:clickable="true"
- android:background="@drawable/wallpaper_tile_fg"
android:foreground="@drawable/wallpaper_tile_fg">
<ImageView
android:id="@+id/wallpaper_image"
diff --git a/WallpaperPicker/res/layout/wallpaper_picker_item.xml b/WallpaperPicker/res/layout/wallpaper_picker_item.xml
index 0ac8f97fb..3f57fcdbd 100644
--- a/WallpaperPicker/res/layout/wallpaper_picker_item.xml
+++ b/WallpaperPicker/res/layout/wallpaper_picker_item.xml
@@ -20,7 +20,6 @@
android:layout_height="@dimen/wallpaperThumbnailHeight"
android:focusable="true"
android:clickable="true"
- android:background="@drawable/wallpaper_tile_fg"
android:foreground="@drawable/wallpaper_tile_fg">
<ImageView
android:id="@+id/wallpaper_image"
diff --git a/WallpaperPicker/res/layout/wallpaper_picker_live_wallpaper_item.xml b/WallpaperPicker/res/layout/wallpaper_picker_live_wallpaper_item.xml
index 29fdb1b82..2b152fce2 100644
--- a/WallpaperPicker/res/layout/wallpaper_picker_live_wallpaper_item.xml
+++ b/WallpaperPicker/res/layout/wallpaper_picker_live_wallpaper_item.xml
@@ -20,7 +20,6 @@
android:layout_height="@dimen/wallpaperThumbnailHeight"
android:focusable="true"
android:clickable="true"
- android:background="@drawable/wallpaper_tile_fg"
android:foreground="@drawable/wallpaper_tile_fg">
<ImageView
android:id="@+id/wallpaper_image"
diff --git a/WallpaperPicker/res/layout/wallpaper_picker_third_party_item.xml b/WallpaperPicker/res/layout/wallpaper_picker_third_party_item.xml
index 68661bc00..a7e3a0c79 100644
--- a/WallpaperPicker/res/layout/wallpaper_picker_third_party_item.xml
+++ b/WallpaperPicker/res/layout/wallpaper_picker_third_party_item.xml
@@ -20,7 +20,6 @@
android:layout_height="@dimen/wallpaperThumbnailHeight"
android:focusable="true"
android:clickable="true"
- android:background="@drawable/wallpaper_tile_fg"
android:foreground="@drawable/wallpaper_tile_fg">
<ImageView
android:id="@+id/wallpaper_image"
diff --git a/WallpaperPicker/res/values-sw720dp-v19/styles.xml b/WallpaperPicker/res/values-sw720dp-v19/styles.xml
index 91078517d..d8dab223a 100644
--- a/WallpaperPicker/res/values-sw720dp-v19/styles.xml
+++ b/WallpaperPicker/res/values-sw720dp-v19/styles.xml
@@ -18,7 +18,7 @@
-->
<resources>
- <style name="Theme" parent="android:Theme.Holo.Wallpaper.NoTitleBar">
+ <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
diff --git a/WallpaperPicker/res/values-sw720dp/styles.xml b/WallpaperPicker/res/values-sw720dp/styles.xml
index b48909087..12f8884f1 100644
--- a/WallpaperPicker/res/values-sw720dp/styles.xml
+++ b/WallpaperPicker/res/values-sw720dp/styles.xml
@@ -18,7 +18,7 @@
-->
<resources>
- <style name="Theme" parent="android:Theme.Holo.Wallpaper.NoTitleBar">
+ <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
<item name="android:windowActionModeOverlay">true</item>
</style>
</resources>
diff --git a/WallpaperPicker/src/android/util/Pools.java b/WallpaperPicker/src/android/util/Pools.java
deleted file mode 100644
index 40bab1eae..000000000
--- a/WallpaperPicker/src/android/util/Pools.java
+++ /dev/null
@@ -1,165 +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 android.util;
-
-/**
- * Helper class for crating pools of objects. An example use looks like this:
- * <pre>
- * public class MyPooledClass {
- *
- * private static final SynchronizedPool<MyPooledClass> sPool =
- * new SynchronizedPool<MyPooledClass>(10);
- *
- * public static MyPooledClass obtain() {
- * MyPooledClass instance = sPool.acquire();
- * return (instance != null) ? instance : new MyPooledClass();
- * }
- *
- * public void recycle() {
- * // Clear state if needed.
- * sPool.release(this);
- * }
- *
- * . . .
- * }
- * </pre>
- *
- * @hide
- */
-public final class Pools {
-
- /**
- * Interface for managing a pool of objects.
- *
- * @param <T> The pooled type.
- */
- public static interface Pool<T> {
-
- /**
- * @return An instance from the pool if such, null otherwise.
- */
- public T acquire();
-
- /**
- * Release an instance to the pool.
- *
- * @param instance The instance to release.
- * @return Whether the instance was put in the pool.
- *
- * @throws IllegalStateException If the instance is already in the pool.
- */
- public boolean release(T instance);
- }
-
- private Pools() {
- /* do nothing - hiding constructor */
- }
-
- /**
- * Simple (non-synchronized) pool of objects.
- *
- * @param <T> The pooled type.
- */
- public static class SimplePool<T> implements Pool<T> {
- private final Object[] mPool;
-
- private int mPoolSize;
-
- /**
- * Creates a new instance.
- *
- * @param maxPoolSize The max pool size.
- *
- * @throws IllegalArgumentException If the max pool size is less than zero.
- */
- public SimplePool(int maxPoolSize) {
- if (maxPoolSize <= 0) {
- throw new IllegalArgumentException("The max pool size must be > 0");
- }
- mPool = new Object[maxPoolSize];
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public T acquire() {
- if (mPoolSize > 0) {
- final int lastPooledIndex = mPoolSize - 1;
- T instance = (T) mPool[lastPooledIndex];
- mPool[lastPooledIndex] = null;
- mPoolSize--;
- return instance;
- }
- return null;
- }
-
- @Override
- public boolean release(T instance) {
- if (isInPool(instance)) {
- throw new IllegalStateException("Already in the pool!");
- }
- if (mPoolSize < mPool.length) {
- mPool[mPoolSize] = instance;
- mPoolSize++;
- return true;
- }
- return false;
- }
-
- private boolean isInPool(T instance) {
- for (int i = 0; i < mPoolSize; i++) {
- if (mPool[i] == instance) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Synchronized) pool of objects.
- *
- * @param <T> The pooled type.
- */
- public static class SynchronizedPool<T> extends SimplePool<T> {
- private final Object mLock = new Object();
-
- /**
- * Creates a new instance.
- *
- * @param maxPoolSize The max pool size.
- *
- * @throws IllegalArgumentException If the max pool size is less than zero.
- */
- public SynchronizedPool(int maxPoolSize) {
- super(maxPoolSize);
- }
-
- @Override
- public T acquire() {
- synchronized (mLock) {
- return super.acquire();
- }
- }
-
- @Override
- public boolean release(T element) {
- synchronized (mLock) {
- return super.release(element);
- }
- }
- }
-} \ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java
new file mode 100644
index 000000000..45118bf45
--- /dev/null
+++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapCropTask.java
@@ -0,0 +1,405 @@
+/**
+ * Copyright (C) 2015 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.gallery3d.common;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
+
+ public interface OnBitmapCroppedHandler {
+ public void onBitmapCropped(byte[] imageBytes);
+ }
+
+ private static final int DEFAULT_COMPRESS_QUALITY = 90;
+ private static final String LOGTAG = "BitmapCropTask";
+
+ Uri mInUri = null;
+ Context mContext;
+ String mInFilePath;
+ byte[] mInImageBytes;
+ int mInResId = 0;
+ RectF mCropBounds = null;
+ int mOutWidth, mOutHeight;
+ int mRotation;
+ boolean mSetWallpaper;
+ boolean mSaveCroppedBitmap;
+ Bitmap mCroppedBitmap;
+ Runnable mOnEndRunnable;
+ Resources mResources;
+ BitmapCropTask.OnBitmapCroppedHandler mOnBitmapCroppedHandler;
+ boolean mNoCrop;
+
+ public BitmapCropTask(Context c, String filePath,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInFilePath = filePath;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(byte[] imageBytes,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mInImageBytes = imageBytes;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(Context c, Uri inUri,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInUri = inUri;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(Context c, Resources res, int inResId,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInResId = inResId;
+ mResources = res;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mCropBounds = cropBounds;
+ mRotation = rotation;
+ mOutWidth = outWidth;
+ mOutHeight = outHeight;
+ mSetWallpaper = setWallpaper;
+ mSaveCroppedBitmap = saveCroppedBitmap;
+ mOnEndRunnable = onEndRunnable;
+ }
+
+ public void setOnBitmapCropped(BitmapCropTask.OnBitmapCroppedHandler handler) {
+ mOnBitmapCroppedHandler = handler;
+ }
+
+ public void setNoCrop(boolean value) {
+ mNoCrop = value;
+ }
+
+ public void setOnEndRunnable(Runnable onEndRunnable) {
+ mOnEndRunnable = onEndRunnable;
+ }
+
+ // Helper to setup input stream
+ private InputStream regenerateInputStream() {
+ if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
+ Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
+ "image byte array given");
+ } else {
+ try {
+ if (mInUri != null) {
+ return new BufferedInputStream(
+ mContext.getContentResolver().openInputStream(mInUri));
+ } else if (mInFilePath != null) {
+ return mContext.openFileInput(mInFilePath);
+ } else if (mInImageBytes != null) {
+ return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
+ } else {
+ return new BufferedInputStream(mResources.openRawResource(mInResId));
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+ }
+ }
+ return null;
+ }
+
+ public Point getImageBounds() {
+ InputStream is = regenerateInputStream();
+ if (is != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ Utils.closeSilently(is);
+ if (options.outWidth != 0 && options.outHeight != 0) {
+ return new Point(options.outWidth, options.outHeight);
+ }
+ }
+ return null;
+ }
+
+ public void setCropBounds(RectF cropBounds) {
+ mCropBounds = cropBounds;
+ }
+
+ public Bitmap getCroppedBitmap() {
+ return mCroppedBitmap;
+ }
+ public boolean cropBitmap() {
+ boolean failure = false;
+
+
+ WallpaperManager wallpaperManager = null;
+ if (mSetWallpaper) {
+ wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
+ }
+
+
+ if (mSetWallpaper && mNoCrop) {
+ try {
+ InputStream is = regenerateInputStream();
+ if (is != null) {
+ wallpaperManager.setStream(is);
+ Utils.closeSilently(is);
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ return !failure;
+ } else {
+ // Find crop bounds (scaled to original image size)
+ Rect roundedTrueCrop = new Rect();
+ Matrix rotateMatrix = new Matrix();
+ Matrix inverseRotateMatrix = new Matrix();
+
+ Point bounds = getImageBounds();
+ if (mRotation > 0) {
+ rotateMatrix.setRotate(mRotation);
+ inverseRotateMatrix.setRotate(-mRotation);
+
+ mCropBounds.roundOut(roundedTrueCrop);
+ mCropBounds = new RectF(roundedTrueCrop);
+
+ if (bounds == null) {
+ Log.w(LOGTAG, "cannot get bounds for image");
+ failure = true;
+ return false;
+ }
+
+ float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+ rotateMatrix.mapPoints(rotatedBounds);
+ rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+ rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+ mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
+ inverseRotateMatrix.mapRect(mCropBounds);
+ mCropBounds.offset(bounds.x/2, bounds.y/2);
+
+ }
+
+ mCropBounds.roundOut(roundedTrueCrop);
+
+ if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+ Log.w(LOGTAG, "crop has bad values for full size image");
+ failure = true;
+ return false;
+ }
+
+ // See how much we're reducing the size of the image
+ int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
+ roundedTrueCrop.height() / mOutHeight));
+ // Attempt to open a region decoder
+ BitmapRegionDecoder decoder = null;
+ InputStream is = null;
+ try {
+ is = regenerateInputStream();
+ if (is == null) {
+ Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
+ failure = true;
+ return false;
+ }
+ decoder = BitmapRegionDecoder.newInstance(is, false);
+ Utils.closeSilently(is);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+ } finally {
+ Utils.closeSilently(is);
+ is = null;
+ }
+
+ Bitmap crop = null;
+ if (decoder != null) {
+ // Do region decoding to get crop bitmap
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ crop = decoder.decodeRegion(roundedTrueCrop, options);
+ decoder.recycle();
+ }
+
+ if (crop == null) {
+ // BitmapRegionDecoder has failed, try to crop in-memory
+ is = regenerateInputStream();
+ Bitmap fullSize = null;
+ if (is != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ fullSize = BitmapFactory.decodeStream(is, null, options);
+ Utils.closeSilently(is);
+ }
+ if (fullSize != null) {
+ // Find out the true sample size that was used by the decoder
+ scaleDownSampleSize = bounds.x / fullSize.getWidth();
+ mCropBounds.left /= scaleDownSampleSize;
+ mCropBounds.top /= scaleDownSampleSize;
+ mCropBounds.bottom /= scaleDownSampleSize;
+ mCropBounds.right /= scaleDownSampleSize;
+ mCropBounds.roundOut(roundedTrueCrop);
+
+ // Adjust values to account for issues related to rounding
+ if (roundedTrueCrop.width() > fullSize.getWidth()) {
+ // Adjust the width
+ roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
+ }
+ if (roundedTrueCrop.right > fullSize.getWidth()) {
+ // Adjust the left value
+ int adjustment = roundedTrueCrop.left -
+ Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
+ roundedTrueCrop.left -= adjustment;
+ roundedTrueCrop.right -= adjustment;
+ }
+ if (roundedTrueCrop.height() > fullSize.getHeight()) {
+ // Adjust the height
+ roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
+ }
+ if (roundedTrueCrop.bottom > fullSize.getHeight()) {
+ // Adjust the top value
+ int adjustment = roundedTrueCrop.top -
+ Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
+ roundedTrueCrop.top -= adjustment;
+ roundedTrueCrop.bottom -= adjustment;
+ }
+
+ crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+ roundedTrueCrop.top, roundedTrueCrop.width(),
+ roundedTrueCrop.height());
+ }
+ }
+
+ if (crop == null) {
+ Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
+ failure = true;
+ return false;
+ }
+ if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
+ float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
+ rotateMatrix.mapPoints(dimsAfter);
+ dimsAfter[0] = Math.abs(dimsAfter[0]);
+ dimsAfter[1] = Math.abs(dimsAfter[1]);
+
+ if (!(mOutWidth > 0 && mOutHeight > 0)) {
+ mOutWidth = Math.round(dimsAfter[0]);
+ mOutHeight = Math.round(dimsAfter[1]);
+ }
+
+ RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
+ RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
+
+ Matrix m = new Matrix();
+ if (mRotation == 0) {
+ m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+ } else {
+ Matrix m1 = new Matrix();
+ m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
+ Matrix m2 = new Matrix();
+ m2.setRotate(mRotation);
+ Matrix m3 = new Matrix();
+ m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
+ Matrix m4 = new Matrix();
+ m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+
+ Matrix c1 = new Matrix();
+ c1.setConcat(m2, m1);
+ Matrix c2 = new Matrix();
+ c2.setConcat(m4, m3);
+ m.setConcat(c2, c1);
+ }
+
+ Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+ (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+ if (tmp != null) {
+ Canvas c = new Canvas(tmp);
+ Paint p = new Paint();
+ p.setFilterBitmap(true);
+ c.drawBitmap(crop, m, p);
+ crop = tmp;
+ }
+ }
+
+ if (mSaveCroppedBitmap) {
+ mCroppedBitmap = crop;
+ }
+
+ // Compress to byte array
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (crop.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+ // If we need to set to the wallpaper, set it
+ if (mSetWallpaper && wallpaperManager != null) {
+ try {
+ byte[] outByteArray = tmpOut.toByteArray();
+ wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
+ if (mOnBitmapCroppedHandler != null) {
+ mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ }
+ } else {
+ Log.w(LOGTAG, "cannot compress bitmap");
+ failure = true;
+ }
+ }
+ return !failure; // True if any of the operations failed
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ return cropBitmap();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (mOnEndRunnable != null) {
+ mOnEndRunnable.run();
+ }
+ }
+} \ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
index a671ed2b9..9ac5c1bf7 100644
--- a/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
+++ b/WallpaperPicker/src/com/android/gallery3d/common/BitmapUtils.java
@@ -16,87 +16,24 @@
package com.android.gallery3d.common;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.os.Build;
-import android.util.FloatMath;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
import android.util.Log;
-import java.io.ByteArrayOutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import com.android.gallery3d.exif.ExifInterface;
-public class BitmapUtils {
- private static final String TAG = "BitmapUtils";
- private static final int DEFAULT_JPEG_QUALITY = 90;
- public static final int UNCONSTRAINED = -1;
-
- private BitmapUtils(){}
-
- /*
- * Compute the sample size as a function of minSideLength
- * and maxNumOfPixels.
- * minSideLength is used to specify that minimal width or height of a
- * bitmap.
- * maxNumOfPixels is used to specify the maximal size in pixels that is
- * tolerable in terms of memory usage.
- *
- * The function returns a sample size based on the constraints.
- * Both size and minSideLength can be passed in as UNCONSTRAINED,
- * which indicates no care of the corresponding constraint.
- * The functions prefers returning a sample size that
- * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
- *
- * Also, the function rounds up the sample size to a power of 2 or multiple
- * of 8 because BitmapFactory only honors sample size this way.
- * For example, BitmapFactory downsamples an image by 2 even though the
- * request is 3. So we round up the sample size to avoid OOM.
- */
- public static int computeSampleSize(int width, int height,
- int minSideLength, int maxNumOfPixels) {
- int initialSize = computeInitialSampleSize(
- width, height, minSideLength, maxNumOfPixels);
-
- return initialSize <= 8
- ? Utils.nextPowerOf2(initialSize)
- : (initialSize + 7) / 8 * 8;
- }
-
- private static int computeInitialSampleSize(int w, int h,
- int minSideLength, int maxNumOfPixels) {
- if (maxNumOfPixels == UNCONSTRAINED
- && minSideLength == UNCONSTRAINED) return 1;
-
- int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
- (int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
-
- if (minSideLength == UNCONSTRAINED) {
- return lowerBound;
- } else {
- int sampleSize = Math.min(w / minSideLength, h / minSideLength);
- return Math.max(sampleSize, lowerBound);
- }
- }
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
- // This computes a sample size which makes the longer side at least
- // minSideLength long. If that's not possible, return 1.
- public static int computeSampleSizeLarger(int w, int h,
- int minSideLength) {
- int initialSize = Math.max(w / minSideLength, h / minSideLength);
- if (initialSize <= 1) return 1;
+public class BitmapUtils {
- return initialSize <= 8
- ? Utils.prevPowerOf2(initialSize)
- : initialSize / 8 * 8;
- }
+ private static final String TAG = "BitmapUtils";
// Find the min x that 1 / x >= scale
public static int computeSampleSizeLarger(float scale) {
- int initialSize = (int) FloatMath.floor(1f / scale);
+ int initialSize = (int) Math.floor(1f / scale);
if (initialSize <= 1) return 1;
return initialSize <= 8
@@ -104,157 +41,41 @@ public class BitmapUtils {
: initialSize / 8 * 8;
}
- // Find the max x that 1 / x <= scale.
- public static int computeSampleSize(float scale) {
- Utils.assertTrue(scale > 0);
- int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
- return initialSize <= 8
- ? Utils.nextPowerOf2(initialSize)
- : (initialSize + 7) / 8 * 8;
- }
-
- public static Bitmap resizeBitmapByScale(
- Bitmap bitmap, float scale, boolean recycle) {
- int width = Math.round(bitmap.getWidth() * scale);
- int height = Math.round(bitmap.getHeight() * scale);
- if (width == bitmap.getWidth()
- && height == bitmap.getHeight()) return bitmap;
- Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
- Canvas canvas = new Canvas(target);
- canvas.scale(scale, scale);
- Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
- canvas.drawBitmap(bitmap, 0, 0, paint);
- if (recycle) bitmap.recycle();
- return target;
+ public static int getRotationFromExif(Context context, Uri uri) {
+ return BitmapUtils.getRotationFromExifHelper(null, 0, context, uri);
}
- private static Bitmap.Config getConfig(Bitmap bitmap) {
- Bitmap.Config config = bitmap.getConfig();
- if (config == null) {
- config = Bitmap.Config.ARGB_8888;
- }
- return config;
+ public static int getRotationFromExif(Resources res, int resId) {
+ return BitmapUtils.getRotationFromExifHelper(res, resId, null, null);
}
- public static Bitmap resizeDownBySideLength(
- Bitmap bitmap, int maxLength, boolean recycle) {
- int srcWidth = bitmap.getWidth();
- int srcHeight = bitmap.getHeight();
- float scale = Math.min(
- (float) maxLength / srcWidth, (float) maxLength / srcHeight);
- if (scale >= 1.0f) return bitmap;
- return resizeBitmapByScale(bitmap, scale, recycle);
- }
-
- public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
- if (w == size && h == size) return bitmap;
-
- // scale the image so that the shorter side equals to the target;
- // the longer side will be center-cropped.
- float scale = (float) size / Math.min(w, h);
-
- Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
- int width = Math.round(scale * bitmap.getWidth());
- int height = Math.round(scale * bitmap.getHeight());
- Canvas canvas = new Canvas(target);
- canvas.translate((size - width) / 2f, (size - height) / 2f);
- canvas.scale(scale, scale);
- Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
- canvas.drawBitmap(bitmap, 0, 0, paint);
- if (recycle) bitmap.recycle();
- return target;
- }
-
- public static void recycleSilently(Bitmap bitmap) {
- if (bitmap == null) return;
- try {
- bitmap.recycle();
- } catch (Throwable t) {
- Log.w(TAG, "unable recycle bitmap", t);
- }
- }
-
- public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
- if (rotation == 0) return source;
- int w = source.getWidth();
- int h = source.getHeight();
- Matrix m = new Matrix();
- m.postRotate(rotation);
- Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
- if (recycle) source.recycle();
- return bitmap;
- }
-
- public static Bitmap createVideoThumbnail(String filePath) {
- // MediaMetadataRetriever is available on API Level 8
- // but is hidden until API Level 10
- Class<?> clazz = null;
- Object instance = null;
+ private static int getRotationFromExifHelper(Resources res, int resId, Context context, Uri uri) {
+ ExifInterface ei = new ExifInterface();
+ InputStream is = null;
+ BufferedInputStream bis = null;
try {
- clazz = Class.forName("android.media.MediaMetadataRetriever");
- instance = clazz.newInstance();
-
- Method method = clazz.getMethod("setDataSource", String.class);
- method.invoke(instance, filePath);
-
- // The method name changes between API Level 9 and 10.
- if (Build.VERSION.SDK_INT <= 9) {
- return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
+ if (uri != null) {
+ is = context.getContentResolver().openInputStream(uri);
+ bis = new BufferedInputStream(is);
+ ei.readExif(bis);
} else {
- byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
- if (data != null) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap != null) return bitmap;
- }
- return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
+ is = res.openRawResource(resId);
+ bis = new BufferedInputStream(is);
+ ei.readExif(bis);
}
- } catch (IllegalArgumentException ex) {
- // Assume this is a corrupt video file
- } catch (RuntimeException ex) {
- // Assume this is a corrupt video file.
- } catch (InstantiationException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "createVideoThumbnail", e);
- } finally {
- try {
- if (instance != null) {
- clazz.getMethod("release").invoke(instance);
- }
- } catch (Exception ignored) {
+ Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (ori != null) {
+ return ExifInterface.getRotationForOrientationValue(ori.shortValue());
}
+ } catch (IOException e) {
+ Log.w(TAG, "Getting exif data failed", e);
+ } catch (NullPointerException e) {
+ // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
+ Log.w(TAG, "Getting exif data failed", e);
+ } finally {
+ Utils.closeSilently(bis);
+ Utils.closeSilently(is);
}
- return null;
- }
-
- public static byte[] compressToBytes(Bitmap bitmap) {
- return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
- }
-
- public static byte[] compressToBytes(Bitmap bitmap, int quality) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
- bitmap.compress(CompressFormat.JPEG, quality, baos);
- return baos.toByteArray();
- }
-
- public static boolean isSupportedByRegionDecoder(String mimeType) {
- if (mimeType == null) return false;
- mimeType = mimeType.toLowerCase();
- return mimeType.startsWith("image/") &&
- (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
- }
-
- public static boolean isRotationSupported(String mimeType) {
- if (mimeType == null) return false;
- mimeType = mimeType.toLowerCase();
- return mimeType.equals("image/jpeg");
+ return 0;
}
}
diff --git a/WallpaperPicker/src/com/android/gallery3d/common/Utils.java b/WallpaperPicker/src/com/android/gallery3d/common/Utils.java
index 614a081c8..8466c22cb 100644
--- a/WallpaperPicker/src/com/android/gallery3d/common/Utils.java
+++ b/WallpaperPicker/src/com/android/gallery3d/common/Utils.java
@@ -16,32 +16,16 @@
package com.android.gallery3d.common;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
-import android.os.Build;
+import android.graphics.RectF;
import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
import android.util.Log;
import java.io.Closeable;
import java.io.IOException;
-import java.io.InterruptedIOException;
public class Utils {
private static final String TAG = "Utils";
- private static final String DEBUG_TAG = "GalleryDebug";
-
- private static final long POLY64REV = 0x95AC9329AC4BC9B5L;
- private static final long INITIALCRC = 0xFFFFFFFFFFFFFFFFL;
-
- private static long[] sCrcTable = new long[256];
-
- private static final boolean IS_DEBUG_BUILD =
- Build.TYPE.equals("eng") || Build.TYPE.equals("userdebug");
-
- private static final String MASK_STRING = "********************************";
// Throws AssertionError if the input is false.
public static void assertTrue(boolean cond) {
@@ -50,28 +34,6 @@ public class Utils {
}
}
- // Throws AssertionError with the message. We had a method having the form
- // assertTrue(boolean cond, String message, Object ... args);
- // However a call to that method will cause memory allocation even if the
- // condition is false (due to autoboxing generated by "Object ... args"),
- // so we don't use that anymore.
- public static void fail(String message, Object ... args) {
- throw new AssertionError(
- args.length == 0 ? message : String.format(message, args));
- }
-
- // Throws NullPointerException if the input is null.
- public static <T> T checkNotNull(T object) {
- if (object == null) throw new NullPointerException();
- return object;
- }
-
- // Returns true if two input Object are both null or equal
- // to each other.
- public static boolean equals(Object a, Object b) {
- return (a == b) || (a == null ? false : a.equals(b));
- }
-
// Returns the next power of two.
// Returns the input if it is already power of 2.
// Throws IllegalArgumentException if the input is <= 0 or
@@ -102,87 +64,6 @@ public class Utils {
return x;
}
- // Returns the input value x clamped to the range [min, max].
- public static float clamp(float x, float min, float max) {
- if (x > max) return max;
- if (x < min) return min;
- return x;
- }
-
- // Returns the input value x clamped to the range [min, max].
- public static long clamp(long x, long min, long max) {
- if (x > max) return max;
- if (x < min) return min;
- return x;
- }
-
- public static boolean isOpaque(int color) {
- return color >>> 24 == 0xFF;
- }
-
- public static void swap(int[] array, int i, int j) {
- int temp = array[i];
- array[i] = array[j];
- array[j] = temp;
- }
-
- /**
- * A function thats returns a 64-bit crc for string
- *
- * @param in input string
- * @return a 64-bit crc value
- */
- public static final long crc64Long(String in) {
- if (in == null || in.length() == 0) {
- return 0;
- }
- return crc64Long(getBytes(in));
- }
-
- static {
- // http://bioinf.cs.ucl.ac.uk/downloads/crc64/crc64.c
- long part;
- for (int i = 0; i < 256; i++) {
- part = i;
- for (int j = 0; j < 8; j++) {
- long x = ((int) part & 1) != 0 ? POLY64REV : 0;
- part = (part >> 1) ^ x;
- }
- sCrcTable[i] = part;
- }
- }
-
- public static final long crc64Long(byte[] buffer) {
- long crc = INITIALCRC;
- for (int k = 0, n = buffer.length; k < n; ++k) {
- crc = sCrcTable[(((int) crc) ^ buffer[k]) & 0xff] ^ (crc >> 8);
- }
- return crc;
- }
-
- public static byte[] getBytes(String in) {
- byte[] result = new byte[in.length() * 2];
- int output = 0;
- for (char ch : in.toCharArray()) {
- result[output++] = (byte) (ch & 0xFF);
- result[output++] = (byte) (ch >> 8);
- }
- return result;
- }
-
- public static void closeSilently(Closeable c) {
- if (c == null) return;
- try {
- c.close();
- } catch (IOException t) {
- Log.w(TAG, "close fail ", t);
- }
- }
-
- public static int compare(long a, long b) {
- return a < b ? -1 : a == b ? 0 : 1;
- }
-
public static int ceilLog2(float value) {
int i;
for (i = 0; i < 31; i++) {
@@ -199,6 +80,15 @@ public class Utils {
return i - 1;
}
+ public static void closeSilently(Closeable c) {
+ if (c == null) return;
+ try {
+ c.close();
+ } catch (IOException t) {
+ Log.w(TAG, "close fail ", t);
+ }
+ }
+
public static void closeSilently(ParcelFileDescriptor fd) {
try {
if (fd != null) fd.close();
@@ -215,126 +105,25 @@ public class Utils {
}
}
- public static float interpolateAngle(
- float source, float target, float progress) {
- // interpolate the angle from source to target
- // We make the difference in the range of [-179, 180], this is the
- // shortest path to change source to target.
- float diff = target - source;
- if (diff < 0) diff += 360f;
- if (diff > 180) diff -= 360f;
-
- float result = source + diff * progress;
- return result < 0 ? result + 360f : result;
- }
-
- public static float interpolateScale(
- float source, float target, float progress) {
- return source + progress * (target - source);
- }
-
- public static String ensureNotNull(String value) {
- return value == null ? "" : value;
- }
-
- public static float parseFloatSafely(String content, float defaultValue) {
- if (content == null) return defaultValue;
- try {
- return Float.parseFloat(content);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- public static int parseIntSafely(String content, int defaultValue) {
- if (content == null) return defaultValue;
- try {
- return Integer.parseInt(content);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- public static boolean isNullOrEmpty(String exifMake) {
- return TextUtils.isEmpty(exifMake);
- }
-
- public static void waitWithoutInterrupt(Object object) {
- try {
- object.wait();
- } catch (InterruptedException e) {
- Log.w(TAG, "unexpected interrupt: " + object);
- }
- }
-
- public static boolean handleInterrruptedException(Throwable e) {
- // A helper to deal with the interrupt exception
- // If an interrupt detected, we will setup the bit again.
- if (e instanceof InterruptedIOException
- || e instanceof InterruptedException) {
- Thread.currentThread().interrupt();
- return true;
- }
- return false;
- }
-
- /**
- * @return String with special XML characters escaped.
- */
- public static String escapeXml(String s) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0, len = s.length(); i < len; ++i) {
- char c = s.charAt(i);
- switch (c) {
- case '<': sb.append("&lt;"); break;
- case '>': sb.append("&gt;"); break;
- case '\"': sb.append("&quot;"); break;
- case '\'': sb.append("&#039;"); break;
- case '&': sb.append("&amp;"); break;
- default: sb.append(c);
- }
- }
- return sb.toString();
- }
-
- public static String getUserAgent(Context context) {
- PackageInfo packageInfo;
- try {
- packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- throw new IllegalStateException("getPackageInfo failed");
- }
- return String.format("%s/%s; %s/%s/%s/%s; %s/%s/%s",
- packageInfo.packageName,
- packageInfo.versionName,
- Build.BRAND,
- Build.DEVICE,
- Build.MODEL,
- Build.ID,
- Build.VERSION.SDK_INT,
- Build.VERSION.RELEASE,
- Build.VERSION.INCREMENTAL);
- }
-
- public static String[] copyOf(String[] source, int newSize) {
- String[] result = new String[newSize];
- newSize = Math.min(source.length, newSize);
- System.arraycopy(source, 0, result, 0, newSize);
- return result;
- }
-
- // Mask information for debugging only. It returns <code>info.toString()</code> directly
- // for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
- // in release build to protect the information (e.g. for privacy issue).
- public static String maskDebugInfo(Object info) {
- if (info == null) return null;
- String s = info.toString();
- int length = Math.min(s.length(), MASK_STRING.length());
- return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
- }
-
- // This method should be ONLY used for debugging.
- public static void debug(String message, Object ... args) {
- Log.v(DEBUG_TAG, String.format(message, args));
+ public static RectF getMaxCropRect(
+ int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
+ RectF cropRect = new RectF();
+ // Get a crop rect that will fit this
+ if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
+ cropRect.top = 0;
+ cropRect.bottom = inHeight;
+ cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
+ cropRect.right = inWidth - cropRect.left;
+ if (leftAligned) {
+ cropRect.right -= cropRect.left;
+ cropRect.left = 0;
+ }
+ } else {
+ cropRect.left = 0;
+ cropRect.right = inWidth;
+ cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
+ cropRect.bottom = inHeight - cropRect.top;
+ }
+ return cropRect;
}
}
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
index 2e77b903f..0f3efb727 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -27,7 +27,6 @@ import java.util.WeakHashMap;
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
public abstract class BasicTexture implements Texture {
- @SuppressWarnings("unused")
private static final String TAG = "BasicTexture";
protected static final int UNSPECIFIED = -1;
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
index 4ead1315e..933260b48 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -23,8 +23,6 @@ import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;
-import com.android.gallery3d.util.IntArray;
-
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -698,6 +696,7 @@ public class GLES20Canvas implements GLCanvas {
}
private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+ deleteRecycledResources();
GLES20.glUseProgram(program);
checkError();
enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
diff --git a/WallpaperPicker/src/com/android/gallery3d/util/IntArray.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/IntArray.java
index 2c4dc2c83..f123624d6 100644
--- a/WallpaperPicker/src/com/android/gallery3d/util/IntArray.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/IntArray.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.gallery3d.util;
+package com.android.gallery3d.glrenderer;
public class IntArray {
private static final int INIT_CAPACITY = 8;
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
index f41a979b7..67abf6564 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -20,6 +20,8 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;
+import com.android.launcher3.util.Thunk;
+
import junit.framework.Assert;
import java.util.HashMap;
@@ -82,7 +84,7 @@ public abstract class UploadedTexture extends BasicTexture {
return mIsUploading;
}
- private static class BorderKey implements Cloneable {
+ @Thunk static class BorderKey implements Cloneable {
public boolean vertical;
public Config config;
public int length;
diff --git a/WallpaperPicker/src/com/android/launcher3/CropView.java b/WallpaperPicker/src/com/android/launcher3/CropView.java
index 578b8eafd..50f779add 100644
--- a/WallpaperPicker/src/com/android/launcher3/CropView.java
+++ b/WallpaperPicker/src/com/android/launcher3/CropView.java
@@ -21,7 +21,6 @@ import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
-import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
@@ -300,12 +299,12 @@ public class CropView extends TiledImageView implements OnScaleGestureListener {
adjustment[0] = (edges.right - getWidth()) / scale;
}
if (edges.top > 0) {
- adjustment[1] = FloatMath.ceil(edges.top / scale);
+ adjustment[1] = (float) Math.ceil(edges.top / scale);
} else if (edges.bottom < getHeight()) {
adjustment[1] = (edges.bottom - getHeight()) / scale;
}
for (int dim = 0; dim <= 1; dim++) {
- if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
+ if (coef[dim] > 0) adjustment[dim] = (float) Math.ceil(adjustment[dim]);
}
mInverseRotateMatrix.mapPoints(adjustment);
diff --git a/src/com/android/launcher3/LauncherWallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/LauncherWallpaperPickerActivity.java
index 10fe013ee..091c05462 100644
--- a/src/com/android/launcher3/LauncherWallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/LauncherWallpaperPickerActivity.java
@@ -16,15 +16,6 @@
package com.android.launcher3;
-import android.content.Intent;
-
+// TODO: Remove this class
public class LauncherWallpaperPickerActivity extends WallpaperPickerActivity {
- @Override
- public void startActivityForResultSafely(Intent intent, int requestCode) {
- Utilities.startActivityForResultSafely(this, intent, requestCode);
- }
- @Override
- public boolean enableRotation() {
- return Utilities.isRotationEnabled(this);
- }
-}
+} \ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
index 88f4461bf..b53fce119 100644
--- a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
+++ b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
@@ -30,11 +30,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.TextView;
+import com.android.launcher3.util.Thunk;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
@@ -50,7 +51,7 @@ public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter
private final LayoutInflater mInflater;
private final PackageManager mPackageManager;
- private List<LiveWallpaperTile> mWallpapers;
+ @Thunk List<LiveWallpaperTile> mWallpapers;
@SuppressWarnings("unchecked")
public LiveWallpaperListAdapter(Context context) {
@@ -90,8 +91,6 @@ public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter
view = convertView;
}
- WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
-
LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
wallpaperInfo.setView(view);
ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
@@ -111,8 +110,8 @@ public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter
}
public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
- private Drawable mThumbnail;
- private WallpaperInfo mInfo;
+ @Thunk Drawable mThumbnail;
+ @Thunk WallpaperInfo mInfo;
public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
mThumbnail = thumbnail;
mInfo = info;
@@ -122,8 +121,8 @@ public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter
Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
mInfo.getComponent());
- a.onLiveWallpaperPickerLaunch(mInfo);
- a.startActivityForResultSafely(preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
+ a.startActivityForResultSafely(preview,
+ WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
}
}
diff --git a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
index 9f92bc105..64b0ac466 100644
--- a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -60,13 +59,13 @@ public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
}
}
- public SavedWallpaperImages(Activity context) {
+ public SavedWallpaperImages(Context context) {
// We used to store the saved images in the cache directory, but that meant they'd get
// deleted sometimes-- move them to the data directory
ImageDb.moveFromCacheDirectoryIfNecessary(context);
mDb = new ImageDb(context);
mContext = context;
- mLayoutInflater = context.getLayoutInflater();
+ mLayoutInflater = LayoutInflater.from(context);
}
public void loadThumbnailsAndImageIdList() {
diff --git a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
index 7a4d48ca9..f46da53ec 100644
--- a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
+++ b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
@@ -28,16 +28,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
import android.widget.ListAdapter;
import android.widget.TextView;
+import com.android.launcher3.util.Thunk;
+
import java.util.ArrayList;
import java.util.List;
public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
- private static final String LOG_TAG = "LiveWallpaperListAdapter";
-
private final LayoutInflater mInflater;
private final PackageManager mPackageManager;
private final int mIconSize;
@@ -46,7 +45,7 @@ public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements
new ArrayList<ThirdPartyWallpaperTile>();
public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
- private ResolveInfo mResolveInfo;
+ @Thunk ResolveInfo mResolveInfo;
public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
mResolveInfo = resolveInfo;
}
@@ -62,7 +61,7 @@ public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements
}
public ThirdPartyWallpaperPickerListAdapter(Context context) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = LayoutInflater.from(context);
mPackageManager = context.getPackageManager();
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
final PackageManager pm = mPackageManager;
@@ -126,8 +125,6 @@ public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements
view = convertView;
}
- WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
-
ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo;
TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
label.setText(info.loadLabel(mPackageManager));
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index fa8ec64c2..142a9cb10 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.app.WallpaperManager;
@@ -25,42 +26,41 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
-import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.View;
-import android.view.WindowManager;
import android.widget.Toast;
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.exif.ExifInterface;
+import com.android.launcher3.base.BaseActivity;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.WallpaperUtils;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
+import com.android.photos.views.TiledImageRenderer.TileSource;
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
-public class WallpaperCropActivity extends Activity {
+public class WallpaperCropActivity extends BaseActivity implements Handler.Callback {
private static final String LOGTAG = "Launcher3.CropActivity";
- protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
- protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
- private static final int DEFAULT_COMPRESS_QUALITY = 90;
+ protected static final String WALLPAPER_WIDTH_KEY = WallpaperUtils.WALLPAPER_WIDTH_KEY;
+ protected static final String WALLPAPER_HEIGHT_KEY = WallpaperUtils.WALLPAPER_HEIGHT_KEY;
+
/**
* The maximum bitmap size we allow to be returned through the intent.
* Intents have a maximum of 1MB in total size. However, the Bitmap seems to
@@ -69,17 +69,31 @@ public class WallpaperCropActivity extends Activity {
* array instead of a Bitmap instance to avoid overhead.
*/
public static final int MAX_BMAP_IN_INTENT = 750000;
- private static final float WALLPAPER_SCREENS_SPAN = 2f;
+ public static final float WALLPAPER_SCREENS_SPAN = WallpaperUtils.WALLPAPER_SCREENS_SPAN;
- protected static Point sDefaultWallpaperSize;
+ private static final int MSG_LOAD_IMAGE = 1;
protected CropView mCropView;
+ protected View mProgressView;
protected Uri mUri;
protected View mSetWallpaperButton;
+ private HandlerThread mLoaderThread;
+ private Handler mLoaderHandler;
+ @Thunk LoadRequest mCurrentLoadRequest;
+ private byte[] mTempStorageForDecoding = new byte[16 * 1024];
+ // A weak-set of reusable bitmaps
+ @Thunk Set<Bitmap> mReusableBitmaps =
+ Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ mLoaderThread = new HandlerThread("wallpaper_loader");
+ mLoaderThread.start();
+ mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
+
init();
if (!enableRotation()) {
setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
@@ -90,6 +104,7 @@ public class WallpaperCropActivity extends Activity {
setContentView(R.layout.wallpaper_cropper);
mCropView = (CropView) findViewById(R.id.cropView);
+ mProgressView = findViewById(R.id.loading);
Intent cropIntent = getIntent();
final Uri imageUri = cropIntent.getData();
@@ -116,13 +131,12 @@ public class WallpaperCropActivity extends Activity {
// Load image in background
final BitmapRegionTileSource.UriBitmapSource bitmapSource =
- new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
+ new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri, 1024);
mSetWallpaperButton.setEnabled(false);
Runnable onLoad = new Runnable() {
public void run() {
if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
- Toast.makeText(WallpaperCropActivity.this,
- getString(R.string.wallpaper_load_fail),
+ Toast.makeText(getContext(), R.string.wallpaper_load_fail,
Toast.LENGTH_LONG).show();
finish();
} else {
@@ -130,188 +144,156 @@ public class WallpaperCropActivity extends Activity {
}
}
};
- setCropViewTileSource(bitmapSource, true, false, onLoad);
+ setCropViewTileSource(bitmapSource, true, false, null, onLoad);
}
@Override
- protected void onDestroy() {
+ public void onDestroy() {
if (mCropView != null) {
mCropView.destroy();
}
+ if (mLoaderThread != null) {
+ mLoaderThread.quit();
+ }
super.onDestroy();
}
- public void setCropViewTileSource(
- final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
- final boolean moveToLeft, final Runnable postExecute) {
- final Context context = WallpaperCropActivity.this;
- final View progressView = findViewById(R.id.loading);
- final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void...args) {
- if (!isCancelled()) {
- try {
- bitmapSource.loadInBackground();
- } catch (SecurityException securityException) {
- if (isDestroyed()) {
- // Temporarily granted permissions are revoked when the activity
- // finishes, potentially resulting in a SecurityException here.
- // Even though {@link #isDestroyed} might also return true in different
- // situations where the configuration changes, we are fine with
- // catching these cases here as well.
- cancel(false);
- } else {
- // otherwise it had a different cause and we throw it further
- throw securityException;
- }
- }
- }
- return null;
- }
- protected void onPostExecute(Void arg) {
- if (!isCancelled()) {
- progressView.setVisibility(View.INVISIBLE);
- if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
- mCropView.setTileSource(
- new BitmapRegionTileSource(context, bitmapSource), null);
- mCropView.setTouchEnabled(touchEnabled);
- if (moveToLeft) {
- mCropView.moveToLeft();
+ /**
+ * This is called on {@link #mLoaderThread}
+ */
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_LOAD_IMAGE) {
+ final LoadRequest req = (LoadRequest) msg.obj;
+ try {
+ req.src.loadInBackground(new InBitmapProvider() {
+
+ @Override
+ public Bitmap forPixelCount(int count) {
+ Bitmap bitmapToReuse = null;
+ // Find the smallest bitmap that satisfies the pixel count limit
+ synchronized (mReusableBitmaps) {
+ int currentBitmapSize = Integer.MAX_VALUE;
+ for (Bitmap b : mReusableBitmaps) {
+ int bitmapSize = b.getWidth() * b.getHeight();
+ if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) {
+ bitmapToReuse = b;
+ currentBitmapSize = bitmapSize;
+ }
+ }
+
+ if (bitmapToReuse != null) {
+ mReusableBitmaps.remove(bitmapToReuse);
+ }
}
+ return bitmapToReuse;
}
- }
- if (postExecute != null) {
- postExecute.run();
- }
- }
- };
- // We don't want to show the spinner every time we load an image, because that would be
- // annoying; instead, only start showing the spinner if loading the image has taken
- // longer than 1 sec (ie 1000 ms)
- progressView.postDelayed(new Runnable() {
- public void run() {
- if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
- progressView.setVisibility(View.VISIBLE);
+ });
+ } catch (SecurityException securityException) {
+ if (isDestroyed()) {
+ // Temporarily granted permissions are revoked when the activity
+ // finishes, potentially resulting in a SecurityException here.
+ // Even though {@link #isDestroyed} might also return true in different
+ // situations where the configuration changes, we are fine with
+ // catching these cases here as well.
+ return true;
+ } else {
+ // otherwise it had a different cause and we throw it further
+ throw securityException;
}
}
- }, 1000);
- loadBitmapTask.execute();
- }
- public boolean enableRotation() {
- return getResources().getBoolean(R.bool.allow_rotation);
- }
+ req.result = new BitmapRegionTileSource(getContext(), req.src, mTempStorageForDecoding);
+ runOnUiThread(new Runnable() {
- public static String getSharedPreferencesKey() {
- return LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY;
+ @Override
+ public void run() {
+ if (req == mCurrentLoadRequest) {
+ onLoadRequestComplete(req,
+ req.src.getLoadingState() == BitmapSource.State.LOADED);
+ } else {
+ addReusableBitmap(req.result);
+ }
+ }
+ });
+ return true;
+ }
+ return false;
}
- // As a ratio of screen height, the total distance we want the parallax effect to span
- // horizontally
- private static 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;
+ @Thunk void addReusableBitmap(TileSource src) {
+ synchronized (mReusableBitmaps) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+ && src instanceof BitmapRegionTileSource) {
+ Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
+ if (preview != null && preview.isMutable()) {
+ mReusableBitmaps.add(preview);
+ }
+ }
+ }
}
- static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
- if (sDefaultWallpaperSize == null) {
- Point minDims = new Point();
- Point maxDims = new Point();
- windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
-
- int maxDim = Math.max(maxDims.x, maxDims.y);
- int minDim = Math.max(minDims.x, minDims.y);
-
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Point realSize = new Point();
- windowManager.getDefaultDisplay().getRealSize(realSize);
- maxDim = Math.max(realSize.x, realSize.y);
- minDim = Math.min(realSize.x, realSize.y);
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ mCurrentLoadRequest = null;
+ if (success) {
+ TileSource oldSrc = mCropView.getTileSource();
+ mCropView.setTileSource(req.result, null);
+ mCropView.setTouchEnabled(req.touchEnabled);
+ if (req.moveToLeft) {
+ mCropView.moveToLeft();
+ }
+ if (req.scaleProvider != null) {
+ mCropView.setScale(req.scaleProvider.getScale(req.result));
}
- // We need to ensure that there is enough extra space in the wallpaper
- // for the intended parallax effects
- final int defaultWidth, defaultHeight;
- if (isScreenLarge(res)) {
- defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
- defaultHeight = maxDim;
- } else {
- defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
- defaultHeight = maxDim;
+ // Free last image
+ if (oldSrc != null) {
+ // Call yield instead of recycle, as we only want to free GL resource.
+ // We can still reuse the bitmap for decoding any other image.
+ oldSrc.getPreview().yield();
}
- sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight);
+ addReusableBitmap(oldSrc);
+ }
+ if (req.postExecute != null) {
+ req.postExecute.run();
}
- return sDefaultWallpaperSize;
}
- public static int getRotationFromExif(String path) {
- return getRotationFromExifHelper(path, null, 0, null, null);
- }
+ public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
+ boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
+ final LoadRequest req = new LoadRequest();
+ req.moveToLeft = moveToLeft;
+ req.src = bitmapSource;
+ req.touchEnabled = touchEnabled;
+ req.postExecute = postExecute;
+ req.scaleProvider = scaleProvider;
+ mCurrentLoadRequest = req;
- public static int getRotationFromExif(Context context, Uri uri) {
- return getRotationFromExifHelper(null, null, 0, context, uri);
- }
+ // Remove any pending requests
+ mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
+ Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
- public static int getRotationFromExif(Resources res, int resId) {
- return getRotationFromExifHelper(null, res, resId, null, null);
+ // We don't want to show the spinner every time we load an image, because that would be
+ // annoying; instead, only start showing the spinner if loading the image has taken
+ // longer than 1 sec (ie 1000 ms)
+ mProgressView.postDelayed(new Runnable() {
+ public void run() {
+ if (mCurrentLoadRequest == req) {
+ mProgressView.setVisibility(View.VISIBLE);
+ }
+ }
+ }, 1000);
}
- private static int getRotationFromExifHelper(
- String path, Resources res, int resId, Context context, Uri uri) {
- ExifInterface ei = new ExifInterface();
- InputStream is = null;
- BufferedInputStream bis = null;
- try {
- if (path != null) {
- ei.readExif(path);
- } else if (uri != null) {
- is = context.getContentResolver().openInputStream(uri);
- bis = new BufferedInputStream(is);
- ei.readExif(bis);
- } else {
- is = res.openRawResource(resId);
- bis = new BufferedInputStream(is);
- ei.readExif(bis);
- }
- Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
- if (ori != null) {
- return ExifInterface.getRotationForOrientationValue(ori.shortValue());
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "Getting exif data failed", e);
- } catch (NullPointerException e) {
- // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
- Log.w(LOGTAG, "Getting exif data failed", e);
- } finally {
- Utils.closeSilently(bis);
- Utils.closeSilently(is);
- }
- return 0;
+
+ public boolean enableRotation() {
+ return getResources().getBoolean(R.bool.allow_rotation);
}
protected void setWallpaper(Uri uri, final boolean finishActivityWhenDone) {
- int rotation = getRotationFromExif(this, uri);
+ int rotation = BitmapUtils.getRotationFromExif(getContext(), uri);
BitmapCropTask cropTask = new BitmapCropTask(
- this, uri, null, rotation, 0, 0, true, false, null);
+ getContext(), uri, null, rotation, 0, 0, true, false, null);
final Point bounds = cropTask.getImageBounds();
Runnable onEndCrop = new Runnable() {
public void run() {
@@ -331,11 +313,11 @@ public class WallpaperCropActivity extends Activity {
Resources res, int resId, final boolean finishActivityWhenDone) {
// crop this image and scale it down to the default wallpaper size for
// this device
- int rotation = getRotationFromExif(res, resId);
+ int rotation = BitmapUtils.getRotationFromExif(res, resId);
Point inSize = mCropView.getSourceDimensions();
- Point outSize = getDefaultWallpaperSize(getResources(),
+ Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
getWindowManager());
- RectF crop = getMaxCropRect(
+ RectF crop = Utils.getMaxCropRect(
inSize.x, inSize.y, outSize.x, outSize.y, false);
Runnable onEndCrop = new Runnable() {
public void run() {
@@ -348,18 +330,14 @@ public class WallpaperCropActivity extends Activity {
}
}
};
- BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, resId,
crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
cropTask.execute();
}
- private static boolean isScreenLarge(Resources res) {
- Configuration config = res.getConfiguration();
- return config.smallestScreenWidthDp >= 720;
- }
-
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
protected void cropImageAndSetWallpaper(Uri uri,
- OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+ BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
// Get the crop
boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
@@ -370,7 +348,7 @@ public class WallpaperCropActivity extends Activity {
d.getSize(displaySize);
boolean isPortrait = displaySize.x < displaySize.y;
- Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(),
+ Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
getWindowManager());
// Get the crop
RectF cropRect = mCropView.getCrop();
@@ -444,7 +422,7 @@ public class WallpaperCropActivity extends Activity {
}
}
};
- BitmapCropTask cropTask = new BitmapCropTask(this, uri,
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri,
cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
if (onBitmapCroppedHandler != null) {
cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
@@ -452,375 +430,9 @@ public class WallpaperCropActivity extends Activity {
cropTask.execute();
}
- public interface OnBitmapCroppedHandler {
- public void onBitmapCropped(byte[] imageBytes);
- }
-
- protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
- Uri mInUri = null;
- Context mContext;
- String mInFilePath;
- byte[] mInImageBytes;
- int mInResId = 0;
- RectF mCropBounds = null;
- int mOutWidth, mOutHeight;
- int mRotation;
- String mOutputFormat = "jpg"; // for now
- boolean mSetWallpaper;
- boolean mSaveCroppedBitmap;
- Bitmap mCroppedBitmap;
- Runnable mOnEndRunnable;
- Resources mResources;
- OnBitmapCroppedHandler mOnBitmapCroppedHandler;
- boolean mNoCrop;
-
- public BitmapCropTask(Context c, String filePath,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInFilePath = filePath;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(byte[] imageBytes,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mInImageBytes = imageBytes;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, Uri inUri,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInUri = inUri;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- public BitmapCropTask(Context c, Resources res, int inResId,
- RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mContext = c;
- mInResId = inResId;
- mResources = res;
- init(cropBounds, rotation,
- outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
- }
-
- private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
- boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
- mCropBounds = cropBounds;
- mRotation = rotation;
- mOutWidth = outWidth;
- mOutHeight = outHeight;
- mSetWallpaper = setWallpaper;
- mSaveCroppedBitmap = saveCroppedBitmap;
- mOnEndRunnable = onEndRunnable;
- }
-
- public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
- mOnBitmapCroppedHandler = handler;
- }
-
- public void setNoCrop(boolean value) {
- mNoCrop = value;
- }
-
- public void setOnEndRunnable(Runnable onEndRunnable) {
- mOnEndRunnable = onEndRunnable;
- }
-
- // Helper to setup input stream
- private InputStream regenerateInputStream() {
- if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
- Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
- "image byte array given");
- } else {
- try {
- if (mInUri != null) {
- return new BufferedInputStream(
- mContext.getContentResolver().openInputStream(mInUri));
- } else if (mInFilePath != null) {
- return mContext.openFileInput(mInFilePath);
- } else if (mInImageBytes != null) {
- return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
- } else {
- return new BufferedInputStream(mResources.openRawResource(mInResId));
- }
- } catch (FileNotFoundException e) {
- Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
- }
- }
- return null;
- }
-
- public Point getImageBounds() {
- InputStream is = regenerateInputStream();
- if (is != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(is, null, options);
- Utils.closeSilently(is);
- if (options.outWidth != 0 && options.outHeight != 0) {
- return new Point(options.outWidth, options.outHeight);
- }
- }
- return null;
- }
-
- public void setCropBounds(RectF cropBounds) {
- mCropBounds = cropBounds;
- }
-
- public Bitmap getCroppedBitmap() {
- return mCroppedBitmap;
- }
- public boolean cropBitmap() {
- boolean failure = false;
-
-
- WallpaperManager wallpaperManager = null;
- if (mSetWallpaper) {
- wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
- }
-
-
- if (mSetWallpaper && mNoCrop) {
- try {
- InputStream is = regenerateInputStream();
- if (is != null) {
- wallpaperManager.setStream(is);
- Utils.closeSilently(is);
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "cannot write stream to wallpaper", e);
- failure = true;
- }
- return !failure;
- } else {
- // Find crop bounds (scaled to original image size)
- Rect roundedTrueCrop = new Rect();
- Matrix rotateMatrix = new Matrix();
- Matrix inverseRotateMatrix = new Matrix();
-
- Point bounds = getImageBounds();
- if (mRotation > 0) {
- rotateMatrix.setRotate(mRotation);
- inverseRotateMatrix.setRotate(-mRotation);
-
- mCropBounds.roundOut(roundedTrueCrop);
- mCropBounds = new RectF(roundedTrueCrop);
-
- if (bounds == null) {
- Log.w(LOGTAG, "cannot get bounds for image");
- failure = true;
- return false;
- }
-
- float[] rotatedBounds = new float[] { bounds.x, bounds.y };
- rotateMatrix.mapPoints(rotatedBounds);
- rotatedBounds[0] = Math.abs(rotatedBounds[0]);
- rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
- mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
- inverseRotateMatrix.mapRect(mCropBounds);
- mCropBounds.offset(bounds.x/2, bounds.y/2);
-
- }
-
- mCropBounds.roundOut(roundedTrueCrop);
-
- if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
- Log.w(LOGTAG, "crop has bad values for full size image");
- failure = true;
- return false;
- }
-
- // See how much we're reducing the size of the image
- int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
- roundedTrueCrop.height() / mOutHeight));
- // Attempt to open a region decoder
- BitmapRegionDecoder decoder = null;
- InputStream is = null;
- try {
- is = regenerateInputStream();
- if (is == null) {
- Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
- failure = true;
- return false;
- }
- decoder = BitmapRegionDecoder.newInstance(is, false);
- Utils.closeSilently(is);
- } catch (IOException e) {
- Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
- } finally {
- Utils.closeSilently(is);
- is = null;
- }
-
- Bitmap crop = null;
- if (decoder != null) {
- // Do region decoding to get crop bitmap
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (scaleDownSampleSize > 1) {
- options.inSampleSize = scaleDownSampleSize;
- }
- crop = decoder.decodeRegion(roundedTrueCrop, options);
- decoder.recycle();
- }
-
- if (crop == null) {
- // BitmapRegionDecoder has failed, try to crop in-memory
- is = regenerateInputStream();
- Bitmap fullSize = null;
- if (is != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (scaleDownSampleSize > 1) {
- options.inSampleSize = scaleDownSampleSize;
- }
- fullSize = BitmapFactory.decodeStream(is, null, options);
- Utils.closeSilently(is);
- }
- if (fullSize != null) {
- // Find out the true sample size that was used by the decoder
- scaleDownSampleSize = bounds.x / fullSize.getWidth();
- mCropBounds.left /= scaleDownSampleSize;
- mCropBounds.top /= scaleDownSampleSize;
- mCropBounds.bottom /= scaleDownSampleSize;
- mCropBounds.right /= scaleDownSampleSize;
- mCropBounds.roundOut(roundedTrueCrop);
-
- // Adjust values to account for issues related to rounding
- if (roundedTrueCrop.width() > fullSize.getWidth()) {
- // Adjust the width
- roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
- }
- if (roundedTrueCrop.right > fullSize.getWidth()) {
- // Adjust the left value
- int adjustment = roundedTrueCrop.left -
- Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
- roundedTrueCrop.left -= adjustment;
- roundedTrueCrop.right -= adjustment;
- }
- if (roundedTrueCrop.height() > fullSize.getHeight()) {
- // Adjust the height
- roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
- }
- if (roundedTrueCrop.bottom > fullSize.getHeight()) {
- // Adjust the top value
- int adjustment = roundedTrueCrop.top -
- Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
- roundedTrueCrop.top -= adjustment;
- roundedTrueCrop.bottom -= adjustment;
- }
-
- crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
- roundedTrueCrop.top, roundedTrueCrop.width(),
- roundedTrueCrop.height());
- }
- }
-
- if (crop == null) {
- Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
- failure = true;
- return false;
- }
- if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
- float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
- rotateMatrix.mapPoints(dimsAfter);
- dimsAfter[0] = Math.abs(dimsAfter[0]);
- dimsAfter[1] = Math.abs(dimsAfter[1]);
-
- if (!(mOutWidth > 0 && mOutHeight > 0)) {
- mOutWidth = Math.round(dimsAfter[0]);
- mOutHeight = Math.round(dimsAfter[1]);
- }
-
- RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
- RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
-
- Matrix m = new Matrix();
- if (mRotation == 0) {
- m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
- } else {
- Matrix m1 = new Matrix();
- m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
- Matrix m2 = new Matrix();
- m2.setRotate(mRotation);
- Matrix m3 = new Matrix();
- m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
- Matrix m4 = new Matrix();
- m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
-
- Matrix c1 = new Matrix();
- c1.setConcat(m2, m1);
- Matrix c2 = new Matrix();
- c2.setConcat(m4, m3);
- m.setConcat(c2, c1);
- }
-
- Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
- (int) returnRect.height(), Bitmap.Config.ARGB_8888);
- if (tmp != null) {
- Canvas c = new Canvas(tmp);
- Paint p = new Paint();
- p.setFilterBitmap(true);
- c.drawBitmap(crop, m, p);
- crop = tmp;
- }
- }
-
- if (mSaveCroppedBitmap) {
- mCroppedBitmap = crop;
- }
-
- // Get output compression format
- CompressFormat cf =
- convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
-
- // Compress to byte array
- ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
- if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
- // If we need to set to the wallpaper, set it
- if (mSetWallpaper && wallpaperManager != null) {
- try {
- byte[] outByteArray = tmpOut.toByteArray();
- wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
- if (mOnBitmapCroppedHandler != null) {
- mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "cannot write stream to wallpaper", e);
- failure = true;
- }
- }
- } else {
- Log.w(LOGTAG, "cannot compress bitmap");
- failure = true;
- }
- }
- return !failure; // True if any of the operations failed
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
- return cropBitmap();
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- if (mOnEndRunnable != null) {
- mOnEndRunnable.run();
- }
- }
- }
-
protected void updateWallpaperDimensions(int width, int height) {
- String spKey = getSharedPreferencesKey();
- SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+ String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY;
+ SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
SharedPreferences.Editor editor = sp.edit();
if (width != 0 && height != 0) {
editor.putInt(WALLPAPER_WIDTH_KEY, width);
@@ -830,69 +442,21 @@ public class WallpaperCropActivity extends Activity {
editor.remove(WALLPAPER_HEIGHT_KEY);
}
editor.commit();
-
- suggestWallpaperDimension(getResources(),
- sp, getWindowManager(), WallpaperManager.getInstance(this), true);
- }
-
- static public void suggestWallpaperDimension(Resources res,
- final SharedPreferences sharedPrefs,
- WindowManager windowManager,
- final WallpaperManager wallpaperManager, boolean fallBackToDefaults) {
- final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
- // If we have saved a wallpaper width/height, use that instead
-
- int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
- int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1);
-
- if (savedWidth == -1 || savedHeight == -1) {
- if (!fallBackToDefaults) {
- return;
- } else {
- savedWidth = defaultWallpaperSize.x;
- savedHeight = defaultWallpaperSize.y;
- }
- }
-
- if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
- savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
- wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
- }
+ WallpaperUtils.suggestWallpaperDimension(getResources(),
+ sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true);
}
- protected static RectF getMaxCropRect(
- int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
- RectF cropRect = new RectF();
- // Get a crop rect that will fit this
- if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
- cropRect.top = 0;
- cropRect.bottom = inHeight;
- cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
- cropRect.right = inWidth - cropRect.left;
- if (leftAligned) {
- cropRect.right -= cropRect.left;
- cropRect.left = 0;
- }
- } else {
- cropRect.left = 0;
- cropRect.right = inWidth;
- cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
- cropRect.bottom = inHeight - cropRect.top;
- }
- return cropRect;
- }
+ static class LoadRequest {
+ BitmapSource src;
+ boolean touchEnabled;
+ boolean moveToLeft;
+ Runnable postExecute;
+ CropViewScaleProvider scaleProvider;
- protected static CompressFormat convertExtensionToCompressFormat(String extension) {
- return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+ TileSource result;
}
- protected static String getFileExtension(String requestFormat) {
- String outputFormat = (requestFormat == null)
- ? "jpg"
- : requestFormat;
- outputFormat = outputFormat.toLowerCase();
- return (outputFormat.equals("png") || outputFormat.equals("gif"))
- ? "png" // We don't support gif compression.
- : "jpg";
+ interface CropViewScaleProvider {
+ float getScale(TileSource src);
}
}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 09e096396..93320919e 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -20,8 +20,8 @@ import android.animation.LayoutTransition;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
-import android.app.WallpaperInfo;
import android.app.WallpaperManager;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -35,11 +35,9 @@ import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PorterDuff;
-import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LevelListDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -70,8 +68,14 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.WallpaperUtils;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.views.TiledImageRenderer.TileSource;
import java.io.File;
import java.io.FileOutputStream;
@@ -83,28 +87,25 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
public static final int IMAGE_PICK = 5;
public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
- public static final int PICK_LIVE_WALLPAPER = 7;
private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
private static final String SELECTED_INDEX = "SELECTED_INDEX";
private static final int FLAG_POST_DELAY_MILLIS = 200;
- private View mSelectedTile;
- private boolean mIgnoreNextTap;
- private OnClickListener mThumbnailOnClickListener;
+ @Thunk View mSelectedTile;
+ @Thunk boolean mIgnoreNextTap;
+ @Thunk OnClickListener mThumbnailOnClickListener;
- private LinearLayout mWallpapersView;
- private View mWallpaperStrip;
+ @Thunk LinearLayout mWallpapersView;
+ @Thunk HorizontalScrollView mWallpaperScrollContainer;
- private ActionMode.Callback mActionModeCallback;
- private ActionMode mActionMode;
+ @Thunk ActionMode.Callback mActionModeCallback;
+ @Thunk ActionMode mActionMode;
- private View.OnLongClickListener mLongClickListener;
+ @Thunk View.OnLongClickListener mLongClickListener;
ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
private SavedWallpaperImages mSavedImages;
- private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
- private int mSelectedIndex = -1;
- private WallpaperInfo mLastClickedLiveWallpaperInfo;
+ @Thunk int mSelectedIndex = -1;
public static abstract class WallpaperTileInfo {
protected View mView;
@@ -136,45 +137,37 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
public static class UriWallpaperInfo extends WallpaperTileInfo {
private Uri mUri;
- private boolean mFirstClick = true;
- private BitmapRegionTileSource.UriBitmapSource mBitmapSource;
public UriWallpaperInfo(Uri uri) {
mUri = uri;
}
@Override
public void onClick(final WallpaperPickerActivity a) {
- final Runnable onLoad;
- if (!mFirstClick) {
- onLoad = null;
- } else {
- mFirstClick = false;
- a.mSetWallpaperButton.setEnabled(false);
- onLoad = new Runnable() {
- public void run() {
- if (mBitmapSource != null &&
- mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
- a.selectTile(mView);
- a.mSetWallpaperButton.setEnabled(true);
- } else {
- ViewGroup parent = (ViewGroup) mView.getParent();
- if (parent != null) {
- parent.removeView(mView);
- Toast.makeText(a,
- a.getString(R.string.image_load_fail),
- Toast.LENGTH_SHORT).show();
- }
+ a.setWallpaperButtonEnabled(false);
+ final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+ new BitmapRegionTileSource.UriBitmapSource(
+ a.getContext(), mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+ a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
+
+ @Override
+ public void run() {
+ if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+ a.selectTile(mView);
+ a.setWallpaperButtonEnabled(true);
+ } else {
+ ViewGroup parent = (ViewGroup) mView.getParent();
+ if (parent != null) {
+ parent.removeView(mView);
+ Toast.makeText(a.getContext(), R.string.image_load_fail,
+ Toast.LENGTH_SHORT).show();
}
}
- };
- }
- mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
- a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
- a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
+ }
+ });
}
@Override
public void onSave(final WallpaperPickerActivity a) {
boolean finishActivityWhenDone = true;
- OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
+ BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.OnBitmapCroppedHandler() {
public void onBitmapCropped(byte[] imageBytes) {
Point thumbSize = getDefaultThumbnailSize(a.getResources());
// rotation is set to 0 since imageBytes has already been correctly rotated
@@ -203,10 +196,18 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
mThumb = thumb;
}
@Override
- public void onClick(WallpaperPickerActivity a) {
+ public void onClick(final WallpaperPickerActivity a) {
+ a.setWallpaperButtonEnabled(false);
BitmapRegionTileSource.UriBitmapSource bitmapSource =
- new BitmapRegionTileSource.UriBitmapSource(a, Uri.fromFile(mFile), 1024);
- a.setCropViewTileSource(bitmapSource, false, true, null);
+ new BitmapRegionTileSource.UriBitmapSource(a.getContext(),
+ Uri.fromFile(mFile), 1024);
+ a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
+
+ @Override
+ public void run() {
+ a.setWallpaperButtonEnabled(true);
+ }
+ });
}
@Override
public void onSave(WallpaperPickerActivity a) {
@@ -232,22 +233,29 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
mThumb = thumb;
}
@Override
- public void onClick(WallpaperPickerActivity a) {
+ public void onClick(final WallpaperPickerActivity a) {
+ a.setWallpaperButtonEnabled(false);
BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
new BitmapRegionTileSource.ResourceBitmapSource(
mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
- bitmapSource.loadInBackground();
- BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
- CropView v = a.getCropView();
- v.setTileSource(source, null);
- Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
- a.getResources(), a.getWindowManager());
- RectF crop = WallpaperCropActivity.getMaxCropRect(
- source.getImageWidth(), source.getImageHeight(),
- wallpaperSize.x, wallpaperSize.y, false);
- v.setScale(wallpaperSize.x / crop.width());
- v.setTouchEnabled(false);
- a.setSystemWallpaperVisiblity(false);
+ a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ Point wallpaperSize = WallpaperUtils.getDefaultWallpaperSize(
+ a.getResources(), a.getWindowManager());
+ RectF crop = Utils.getMaxCropRect(
+ src.getImageWidth(), src.getImageHeight(),
+ wallpaperSize.x, wallpaperSize.y, false);
+ return wallpaperSize.x / crop.width();
+ }
+ }, new Runnable() {
+
+ @Override
+ public void run() {
+ a.setWallpaperButtonEnabled(true);
+ }
+ });
}
@Override
public void onSave(WallpaperPickerActivity a) {
@@ -272,27 +280,33 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
@Override
public void onClick(WallpaperPickerActivity a) {
CropView c = a.getCropView();
-
- Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
- c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
-
+ Drawable defaultWallpaper = WallpaperManager.getInstance(a.getContext())
+ .getBuiltInDrawable(c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
if (defaultWallpaper == null) {
Log.w(TAG, "Null default wallpaper encountered.");
c.setTileSource(null, null);
return;
}
- c.setTileSource(
- new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
- c.setScale(1f);
- c.setTouchEnabled(false);
- a.setSystemWallpaperVisiblity(false);
+ LoadRequest req = new LoadRequest();
+ req.moveToLeft = false;
+ req.touchEnabled = false;
+ req.scaleProvider = new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ return 1f;
+ }
+ };
+ req.result = new DrawableTileSource(a.getContext(),
+ defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+ a.onLoadRequestComplete(req, true);
}
@Override
public void onSave(WallpaperPickerActivity a) {
try {
- WallpaperManager.getInstance(a).clear();
- a.setResult(RESULT_OK);
+ WallpaperManager.getInstance(a.getContext()).clear();
+ a.setResult(Activity.RESULT_OK);
} catch (IOException e) {
Log.w("Setting wallpaper to default threw exception", e);
}
@@ -308,10 +322,6 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
}
- public void setWallpaperStripYOffset(float offset) {
- mWallpaperStrip.setPadding(0, 0, 0, (int) offset);
- }
-
/**
* shows the system wallpaper behind the window and hides the {@link
* #mCropView} if visible
@@ -338,7 +348,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}, FLAG_POST_DELAY_MILLIS);
}
- private void changeWallpaperFlags(boolean visible) {
+ @Thunk void changeWallpaperFlags(boolean visible) {
int desiredWallpaperFlag = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
int currentWallpaperFlag = getWindow().getAttributes().flags
& WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -349,24 +359,11 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
@Override
- public void setCropViewTileSource(BitmapSource bitmapSource,
- boolean touchEnabled,
- boolean moveToLeft,
- final Runnable postExecute) {
- // we also want to show our own wallpaper instead of the one in the background
- Runnable showPostExecuteRunnable = new Runnable() {
- @Override
- public void run() {
- if(postExecute != null) {
- postExecute.run();
- }
- setSystemWallpaperVisiblity(false);
- }
- };
- super.setCropViewTileSource(bitmapSource,
- touchEnabled,
- moveToLeft,
- showPostExecuteRunnable);
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ super.onLoadRequestComplete(req, success);
+ if (success) {
+ setSystemWallpaperVisiblity(false);
+ }
}
// called by onCreate; this is subclassed to overwrite WallpaperCropActivity
@@ -376,7 +373,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
mCropView = (CropView) findViewById(R.id.cropView);
mCropView.setVisibility(View.INVISIBLE);
- mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ mProgressView = findViewById(R.id.loading);
+ mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
mCropView.setTouchCallback(new CropView.TouchCallback() {
ViewPropertyAnimator mAnim;
@Override
@@ -384,15 +382,15 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
if (mAnim != null) {
mAnim.cancel();
}
- if (mWallpaperStrip.getAlpha() == 1f) {
+ if (mWallpaperScrollContainer.getAlpha() == 1f) {
mIgnoreNextTap = true;
}
- mAnim = mWallpaperStrip.animate();
+ mAnim = mWallpaperScrollContainer.animate();
mAnim.alpha(0f)
.setDuration(150)
.withEndAction(new Runnable() {
public void run() {
- mWallpaperStrip.setVisibility(View.INVISIBLE);
+ mWallpaperScrollContainer.setVisibility(View.INVISIBLE);
}
});
mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
@@ -410,8 +408,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
if (mAnim != null) {
mAnim.cancel();
}
- mWallpaperStrip.setVisibility(View.VISIBLE);
- mAnim = mWallpaperStrip.animate();
+ mWallpaperScrollContainer.setVisibility(View.VISIBLE);
+ mAnim = mWallpaperScrollContainer.animate();
mAnim.alpha(1f)
.setDuration(150)
.setInterpolator(new DecelerateInterpolator(0.75f));
@@ -429,7 +427,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
return;
}
- mSetWallpaperButton.setEnabled(true);
+ setWallpaperButtonEnabled(true);
WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
selectTile(v);
@@ -460,18 +458,18 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
// Populate the built-in wallpapers
ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
- SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers);
+ SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(getContext(), wallpapers);
populateWallpapersFromAdapter(mWallpapersView, ia, false);
// Populate the saved wallpapers
- mSavedImages = new SavedWallpaperImages(this);
+ mSavedImages = new SavedWallpaperImages(getContext());
mSavedImages.loadThumbnailsAndImageIdList();
populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
// Populate the live wallpapers
final LinearLayout liveWallpapersView =
(LinearLayout) findViewById(R.id.live_wallpaper_list);
- final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
+ final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(getContext());
a.registerDataSetObserver(new DataSetObserver() {
public void onChanged() {
liveWallpapersView.removeAllViews();
@@ -485,14 +483,13 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
final LinearLayout thirdPartyWallpapersView =
(LinearLayout) findViewById(R.id.third_party_wallpaper_list);
final ThirdPartyWallpaperPickerListAdapter ta =
- new ThirdPartyWallpaperPickerListAdapter(this);
+ new ThirdPartyWallpaperPickerListAdapter(getContext());
populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
// Add a tile for the Gallery
LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
- setWallpaperItemPaddingToZero(pickImageTile);
masterWallpaperList.addView(pickImageTile, 0);
// Make its background the last photo taken on external storage
@@ -500,10 +497,9 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
if (lastPhoto != null) {
ImageView galleryThumbnailBg =
(ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
- galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
+ galleryThumbnailBg.setImageBitmap(lastPhoto);
int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
-
}
PickImageInfo pickImageInfo = new PickImageInfo();
@@ -650,7 +646,11 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
};
}
- private void selectTile(View v) {
+ public void setWallpaperButtonEnabled(boolean enabled) {
+ mSetWallpaperButton.setEnabled(enabled);
+ }
+
+ @Thunk void selectTile(View v) {
if (mSelectedTile != null) {
mSelectedTile.setSelected(false);
mSelectedTile = null;
@@ -661,28 +661,25 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
// TODO: Remove this once the accessibility framework and
// services have better support for selection state.
v.announceForAccessibility(
- getString(R.string.announce_selection, v.getContentDescription()));
+ getContext().getString(R.string.announce_selection, v.getContentDescription()));
}
- private void initializeScrollForRtl() {
- final HorizontalScrollView scroll =
- (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
-
- if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- final ViewTreeObserver observer = scroll.getViewTreeObserver();
+ @Thunk void initializeScrollForRtl() {
+ if (mWallpaperScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ final ViewTreeObserver observer = mWallpaperScrollContainer.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
LinearLayout masterWallpaperList =
(LinearLayout) findViewById(R.id.master_wallpaper_list);
- scroll.scrollTo(masterWallpaperList.getWidth(), 0);
- scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mWallpaperScrollContainer.scrollTo(masterWallpaperList.getWidth(), 0);
+ mWallpaperScrollContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
}
protected Bitmap getThumbnailOfLastPhoto() {
- Cursor cursor = MediaStore.Images.Media.query(getContentResolver(),
+ Cursor cursor = MediaStore.Images.Media.query(getContext().getContentResolver(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.ImageColumns._ID,
MediaStore.Images.ImageColumns.DATE_TAKEN},
@@ -692,7 +689,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
if (cursor != null) {
if (cursor.moveToNext()) {
int id = cursor.getInt(0);
- thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
+ thumb = MediaStore.Images.Thumbnails.getThumbnail(getContext().getContentResolver(),
id, MediaStore.Images.Thumbnails.MINI_KIND, null);
}
cursor.close();
@@ -700,16 +697,16 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
return thumb;
}
- protected void onStop() {
+ public void onStop() {
super.onStop();
- mWallpaperStrip = findViewById(R.id.wallpaper_strip);
- if (mWallpaperStrip.getAlpha() < 1f) {
- mWallpaperStrip.setAlpha(1f);
- mWallpaperStrip.setVisibility(View.VISIBLE);
+ mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+ if (mWallpaperScrollContainer.getAlpha() < 1f) {
+ mWallpaperScrollContainer.setAlpha(1f);
+ mWallpaperScrollContainer.setVisibility(View.VISIBLE);
}
}
- protected void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
outState.putInt(SELECTED_INDEX, mSelectedIndex);
}
@@ -722,7 +719,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1);
}
- private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
+ @Thunk void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
boolean addLongPressHandler) {
for (int i = 0; i < adapter.getCount(); i++) {
FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
@@ -737,7 +734,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
}
- private void updateTileIndices() {
+ @Thunk void updateTileIndices() {
LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
final int childCount = masterWallpaperList.getChildCount();
final Resources res = getResources();
@@ -778,13 +775,13 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
}
- private static Point getDefaultThumbnailSize(Resources res) {
+ @Thunk static Point getDefaultThumbnailSize(Resources res) {
return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
}
- private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
+ @Thunk static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
Resources res, int resId, int rotation, boolean leftAligned) {
int width = size.x;
int height = size.y;
@@ -812,7 +809,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
rotatedBounds[0] = Math.abs(rotatedBounds[0]);
rotatedBounds[1] = Math.abs(rotatedBounds[1]);
- RectF cropRect = WallpaperCropActivity.getMaxCropRect(
+ RectF cropRect = Utils.getMaxCropRect(
(int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
cropTask.setCropBounds(cropRect);
@@ -829,17 +826,16 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
pickedImageThumbnail.setVisibility(View.GONE);
- setWallpaperItemPaddingToZero(pickedImageThumbnail);
mWallpapersView.addView(pickedImageThumbnail, 0);
// Load the thumbnail
final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
final Point defaultSize = getDefaultThumbnailSize(this.getResources());
- final Context context = this;
+ final Context context = getContext();
new AsyncTask<Void, Bitmap, Bitmap>() {
protected Bitmap doInBackground(Void...args) {
try {
- int rotation = WallpaperCropActivity.getRotationFromExif(context, uri);
+ int rotation = BitmapUtils.getRotationFromExif(context, uri);
return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false);
} catch (SecurityException securityException) {
if (isDestroyed()) {
@@ -879,45 +875,26 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
}
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == IMAGE_PICK && resultCode == Activity.RESULT_OK) {
if (data != null && data.getData() != null) {
Uri uri = data.getData();
addTemporaryWallpaperTile(uri, false);
}
- } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
- setResult(RESULT_OK);
+ } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY
+ && resultCode == Activity.RESULT_OK) {
+ // Something was set on the third-party activity.
+ setResult(Activity.RESULT_OK);
finish();
- } else if (requestCode == PICK_LIVE_WALLPAPER) {
- WallpaperManager wm = WallpaperManager.getInstance(this);
- final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch;
- final WallpaperInfo clickedWallpaper = mLastClickedLiveWallpaperInfo;
- WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo();
- // Try to figure out if a live wallpaper was set;
- if (newLiveWallpaper != null &&
- (oldLiveWallpaper == null
- || !oldLiveWallpaper.getComponent()
- .equals(newLiveWallpaper.getComponent())
- || clickedWallpaper.getComponent()
- .equals(oldLiveWallpaper.getComponent()))) {
- // Return if a live wallpaper was set
- setResult(RESULT_OK);
- finish();
- }
}
}
- static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
- frameLayout.setPadding(0, 0, 0, 0);
- frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
- }
-
private void addLongPressHandler(View v) {
v.setOnLongClickListener(mLongClickListener);
}
private ArrayList<WallpaperTileInfo> findBundledWallpapers() {
- final PackageManager pm = getPackageManager();
+ final PackageManager pm = getContext().getPackageManager();
final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
Partner partner = Partner.get(pm);
@@ -961,7 +938,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
if (r != null) {
try {
- Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
+ Resources wallpaperRes = getContext().getPackageManager()
+ .getResourcesForApplication(r.first);
addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
} catch (PackageManager.NameNotFoundException e) {
}
@@ -984,7 +962,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
try {
f.createNewFile();
FileOutputStream thumbFileStream =
- openFileOutput(f.getName(), Context.MODE_PRIVATE);
+ getContext().openFileOutput(f.getName(), Context.MODE_PRIVATE);
b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
thumbFileStream.close();
return true;
@@ -996,17 +974,18 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
}
private File getDefaultThumbFile() {
- return new File(getFilesDir(), Build.VERSION.SDK_INT
+ return new File(getContext().getFilesDir(), Build.VERSION.SDK_INT
+ "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL);
}
private boolean saveDefaultWallpaperThumb(Bitmap b) {
// Delete old thumbnails.
- new File(getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
- new File(getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+ new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
+ new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
- new File(getFilesDir(), i + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+ new File(getContext().getFilesDir(), i + "_"
+ + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
}
return writeImageToFileAsJpeg(getDefaultThumbFile(), b);
}
@@ -1024,9 +1003,9 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
} else {
Resources res = getResources();
Point defaultThumbSize = getDefaultThumbnailSize(res);
- int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
+ int rotation = BitmapUtils.getRotationFromExif(res, resId);
thumb = createThumbnail(
- defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
+ defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false);
if (thumb != null) {
defaultWallpaperExists = saveDefaultWallpaperThumb(thumb);
}
@@ -1048,7 +1027,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
} else {
Resources res = getResources();
Point defaultThumbSize = getDefaultThumbnailSize(res);
- Drawable wallpaperDrawable = WallpaperManager.getInstance(this).getBuiltInDrawable(
+ Drawable wallpaperDrawable = WallpaperManager.getInstance(getContext()).getBuiltInDrawable(
defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
if (wallpaperDrawable != null) {
thumb = Bitmap.createBitmap(
@@ -1075,7 +1054,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
// package name should be.
final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
try {
- ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
+ ApplicationInfo info = getContext().getPackageManager().getApplicationInfo(packageName, 0);
return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
} catch (PackageManager.NameNotFoundException e) {
return null;
@@ -1110,31 +1089,12 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
return mSavedImages;
}
- public void onLiveWallpaperPickerLaunch(WallpaperInfo info) {
- mLastClickedLiveWallpaperInfo = info;
- mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
- }
-
- static class ZeroPaddingDrawable extends LevelListDrawable {
- public ZeroPaddingDrawable(Drawable d) {
- super();
- addLevel(0, 0, d);
- setLevel(0);
- }
-
- @Override
- public boolean getPadding(Rect padding) {
- padding.set(0, 0, 0, 0);
- return true;
- }
- }
-
private static class SimpleWallpapersAdapter extends ArrayAdapter<WallpaperTileInfo> {
private final LayoutInflater mLayoutInflater;
- SimpleWallpapersAdapter(Activity activity, ArrayList<WallpaperTileInfo> wallpapers) {
- super(activity, R.layout.wallpaper_picker_item, wallpapers);
- mLayoutInflater = activity.getLayoutInflater();
+ SimpleWallpapersAdapter(Context context, ArrayList<WallpaperTileInfo> wallpapers) {
+ super(context, R.layout.wallpaper_picker_item, wallpapers);
+ mLayoutInflater = LayoutInflater.from(context);
}
public View getView(int position, View convertView, ViewGroup parent) {
@@ -1156,8 +1116,6 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
view = convertView;
}
- setWallpaperItemPaddingToZero((FrameLayout) view);
-
ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
if (thumb != null) {
@@ -1168,9 +1126,12 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
return view;
}
- // In Launcher3, we override this with a method that catches exceptions
- // from starting activities; didn't want to copy and paste code into here
public void startActivityForResultSafely(Intent intent, int requestCode) {
- startActivityForResult(intent, requestCode);
+ Utilities.startActivityForResultSafely(getActivity(), intent, requestCode);
+ }
+
+ @Override
+ public boolean enableRotation() {
+ return Utilities.isRotationEnabled(getContext());
}
}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java b/WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java
deleted file mode 100644
index ceaa043a7..000000000
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 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.launcher3;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.widget.RelativeLayout;
-
-public class WallpaperRootView extends RelativeLayout {
- private final WallpaperPickerActivity a;
- public WallpaperRootView(Context context, AttributeSet attrs) {
- super(context, attrs);
- a = (WallpaperPickerActivity) context;
- }
- public WallpaperRootView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- a = (WallpaperPickerActivity) context;
- }
-
- protected boolean fitSystemWindows(Rect insets) {
- a.setWallpaperStripYOffset(insets.bottom);
- return true;
- }
-}
diff --git a/WallpaperPicker/src/com/android/launcher3/base/BaseActivity.java b/WallpaperPicker/src/com/android/launcher3/base/BaseActivity.java
new file mode 100644
index 000000000..f8541188f
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/base/BaseActivity.java
@@ -0,0 +1,21 @@
+package com.android.launcher3.base;
+
+import android.app.Activity;
+import android.content.Context;
+
+/**
+ * A wrapper over {@link Activity} which allows to override some methods.
+ * The base implementation can change from an Activity to a Fragment (or any other custom
+ * implementation), Callers should not assume that the base class extends Context, instead use
+ * either {@link #getContext} or {@link #getActivity}
+ */
+public class BaseActivity extends Activity {
+
+ public Context getContext() {
+ return this;
+ }
+
+ public Activity getActivity() {
+ return this;
+ }
+}
diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
index 66ece4ff6..15f97e5b1 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -20,7 +20,6 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
@@ -28,7 +27,6 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
-import android.os.Build.VERSION_CODES;
import android.util.Log;
import com.android.gallery3d.common.BitmapUtils;
@@ -148,8 +146,6 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
private static final String TAG = "BitmapRegionTileSource";
- private static final boolean REUSE_BITMAP =
- Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
private static final int GL_SIZE_LIMIT = 2048;
// This must be no larger than half the size of the GL_SIZE_LIMIT
// due to decodePreview being allowed to be up to 2x the size of the target
@@ -158,14 +154,14 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
public static abstract class BitmapSource {
private SimpleBitmapRegionDecoder mDecoder;
private Bitmap mPreview;
- private int mPreviewSize;
+ private final int mPreviewSize;
private int mRotation;
public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
private State mState = State.NOT_LOADED;
public BitmapSource(int previewSize) {
- mPreviewSize = previewSize;
+ mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
}
- public boolean loadInBackground() {
+ public boolean loadInBackground(InBitmapProvider bitmapProvider) {
ExifInterface ei = new ExifInterface();
if (readExif(ei)) {
Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
@@ -181,15 +177,33 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
int width = mDecoder.getWidth();
int height = mDecoder.getHeight();
if (mPreviewSize != 0) {
- int previewSize = Math.min(mPreviewSize, MAX_PREVIEW_SIZE);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
opts.inPreferQualityOverSpeed = true;
- float scale = (float) previewSize / Math.max(width, height);
+ float scale = (float) mPreviewSize / Math.max(width, height);
opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
opts.inJustDecodeBounds = false;
- mPreview = loadPreviewBitmap(opts);
+ opts.inMutable = true;
+
+ if (bitmapProvider != null) {
+ int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
+ Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
+ if (reusableBitmap != null) {
+ // Try loading with reusable bitmap
+ opts.inBitmap = reusableBitmap;
+ try {
+ mPreview = loadPreviewBitmap(opts);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Unable to reusage bitmap", e);
+ opts.inBitmap = null;
+ mPreview = null;
+ }
+ }
+ }
+ if (mPreview == null) {
+ mPreview = loadPreviewBitmap(opts);
+ }
}
mState = State.LOADED;
return true;
@@ -208,10 +222,6 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
return mPreview;
}
- public int getPreviewSize() {
- return mPreviewSize;
- }
-
public int getRotation() {
return mRotation;
}
@@ -219,6 +229,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
public abstract boolean readExif(ExifInterface ei);
public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
+
+ public interface InBitmapProvider {
+ Bitmap forPixelCount(int count);
+ }
}
public static class FilePathBitmapSource extends BitmapSource {
@@ -306,13 +320,13 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
Utils.closeSilently(is);
return true;
} catch (FileNotFoundException e) {
- Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+ Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
return false;
} catch (IOException e) {
- Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+ Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
return false;
} catch (NullPointerException e) {
- Log.e("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
+ Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
return false;
} finally {
Utils.closeSilently(is);
@@ -372,11 +386,9 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
// For use only by getTile
private Rect mWantRegion = new Rect();
- private Rect mOverlapRegion = new Rect();
private BitmapFactory.Options mOptions;
- private Canvas mCanvas;
- public BitmapRegionTileSource(Context context, BitmapSource source) {
+ public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
mTileSize = TiledImageRenderer.suggestedTileSize(context);
mRotation = source.getRotation();
mDecoder = source.getBitmapRegionDecoder();
@@ -386,27 +398,26 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
mOptions = new BitmapFactory.Options();
mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
mOptions.inPreferQualityOverSpeed = true;
- mOptions.inTempStorage = new byte[16 * 1024];
- int previewSize = source.getPreviewSize();
- if (previewSize != 0) {
- previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
- // Although this is the same size as the Bitmap that is likely already
- // loaded, the lifecycle is different and interactions are on a different
- // thread. Thus to simplify, this source will decode its own bitmap.
- Bitmap preview = decodePreview(source, previewSize);
- if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
+ mOptions.inTempStorage = tempStorage;
+
+ Bitmap preview = source.getPreviewBitmap();
+ if (preview != null &&
+ preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
mPreview = new BitmapTexture(preview);
- } else {
- Log.w(TAG, String.format(
- "Failed to create preview of apropriate size! "
- + " in: %dx%d, out: %dx%d",
- mWidth, mHeight,
- preview.getWidth(), preview.getHeight()));
- }
+ } else {
+ Log.w(TAG, String.format(
+ "Failed to create preview of apropriate size! "
+ + " in: %dx%d, out: %dx%d",
+ mWidth, mHeight,
+ preview.getWidth(), preview.getHeight()));
}
}
}
+ public Bitmap getBitmap() {
+ return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
+ }
+
@Override
public int getTileSize() {
return mTileSize;
@@ -435,10 +446,6 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
@Override
public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
int tileSize = getTileSize();
- if (!REUSE_BITMAP) {
- return getTileWithoutReusingBitmap(level, x, y, tileSize);
- }
-
int t = tileSize << level;
mWantRegion.set(x, y, x + t, y + t);
@@ -462,64 +469,4 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
}
return bitmap;
}
-
- private Bitmap getTileWithoutReusingBitmap(
- int level, int x, int y, int tileSize) {
-
- int t = tileSize << level;
- mWantRegion.set(x, y, x + t, y + t);
-
- mOverlapRegion.set(0, 0, mWidth, mHeight);
-
- mOptions.inSampleSize = (1 << level);
- Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
-
- if (bitmap == null) {
- Log.w(TAG, "fail in decoding region");
- }
-
- if (mWantRegion.equals(mOverlapRegion)) {
- return bitmap;
- }
-
- Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
- if (mCanvas == null) {
- mCanvas = new Canvas();
- }
- mCanvas.setBitmap(result);
- mCanvas.drawBitmap(bitmap,
- (mOverlapRegion.left - mWantRegion.left) >> level,
- (mOverlapRegion.top - mWantRegion.top) >> level, null);
- mCanvas.setBitmap(null);
- return result;
- }
-
- /**
- * Note that the returned bitmap may have a long edge that's longer
- * than the targetSize, but it will always be less than 2x the targetSize
- */
- private Bitmap decodePreview(BitmapSource source, int targetSize) {
- Bitmap result = source.getPreviewBitmap();
- if (result == null) {
- return null;
- }
-
- // We need to resize down if the decoder does not support inSampleSize
- // or didn't support the specified inSampleSize (some decoders only do powers of 2)
- float scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
-
- if (scale <= 0.5) {
- result = BitmapUtils.resizeBitmapByScale(result, scale, true);
- }
- return ensureGLCompatibleBitmap(result);
- }
-
- private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
- if (bitmap == null || bitmap.getConfig() != null) {
- return bitmap;
- }
- Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
- bitmap.recycle();
- return newBitmap;
- }
}
diff --git a/WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java b/WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java
deleted file mode 100644
index 8a0505185..000000000
--- a/WallpaperPicker/src/com/android/photos/views/BlockingGLTextureView.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright (C) 2013 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.photos.views;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.opengl.GLSurfaceView.Renderer;
-import android.opengl.GLUtils;
-import android.util.Log;
-import android.view.TextureView;
-import android.view.TextureView.SurfaceTextureListener;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL10;
-
-/**
- * A TextureView that supports blocking rendering for synchronous drawing
- */
-public class BlockingGLTextureView extends TextureView
- implements SurfaceTextureListener {
-
- private RenderThread mRenderThread;
-
- public BlockingGLTextureView(Context context) {
- super(context);
- setSurfaceTextureListener(this);
- }
-
- public void setRenderer(Renderer renderer) {
- if (mRenderThread != null) {
- throw new IllegalArgumentException("Renderer already set");
- }
- mRenderThread = new RenderThread(renderer);
- }
-
- public void render() {
- mRenderThread.render();
- }
-
- public void destroy() {
- if (mRenderThread != null) {
- mRenderThread.finish();
- mRenderThread = null;
- }
- }
-
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
- int height) {
- mRenderThread.setSurface(surface);
- mRenderThread.setSize(width, height);
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
- int height) {
- mRenderThread.setSize(width, height);
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- if (mRenderThread != null) {
- mRenderThread.setSurface(null);
- }
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } catch (Throwable t) {
- // Ignore
- }
- super.finalize();
- }
-
- /**
- * An EGL helper class.
- */
-
- private static class EglHelper {
- private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- private static final int EGL_OPENGL_ES2_BIT = 4;
-
- EGL10 mEgl;
- EGLDisplay mEglDisplay;
- EGLSurface mEglSurface;
- EGLConfig mEglConfig;
- EGLContext mEglContext;
-
- private EGLConfig chooseEglConfig() {
- int[] configsCount = new int[1];
- EGLConfig[] configs = new EGLConfig[1];
- int[] configSpec = getConfig();
- if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- return configs[0];
- }
- return null;
- }
-
- private static int[] getConfig() {
- return new int[] {
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL10.EGL_RED_SIZE, 8,
- EGL10.EGL_GREEN_SIZE, 8,
- EGL10.EGL_BLUE_SIZE, 8,
- EGL10.EGL_ALPHA_SIZE, 8,
- EGL10.EGL_DEPTH_SIZE, 0,
- EGL10.EGL_STENCIL_SIZE, 0,
- EGL10.EGL_NONE
- };
- }
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
- return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
- }
-
- /**
- * Initialize EGL for a given configuration spec.
- */
- public void start() {
- /*
- * Get an EGL instance
- */
- mEgl = (EGL10) EGLContext.getEGL();
-
- /*
- * Get to the default display.
- */
- mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-
- if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed");
- }
-
- /*
- * We can now initialize EGL for that display
- */
- int[] version = new int[2];
- if (!mEgl.eglInitialize(mEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed");
- }
- mEglConfig = chooseEglConfig();
-
- /*
- * Create an EGL context. We want to do this as rarely as we can, because an
- * EGL context is a somewhat heavy object.
- */
- mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
-
- if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
- mEglContext = null;
- throwEglException("createContext");
- }
-
- mEglSurface = null;
- }
-
- /**
- * Create an egl surface for the current SurfaceTexture surface. If a surface
- * already exists, destroy it before creating the new surface.
- *
- * @return true if the surface was created successfully.
- */
- public boolean createSurface(SurfaceTexture surface) {
- /*
- * Check preconditions.
- */
- if (mEgl == null) {
- throw new RuntimeException("egl not initialized");
- }
- if (mEglDisplay == null) {
- throw new RuntimeException("eglDisplay not initialized");
- }
- if (mEglConfig == null) {
- throw new RuntimeException("mEglConfig not initialized");
- }
-
- /*
- * The window size has changed, so we need to create a new
- * surface.
- */
- destroySurfaceImp();
-
- /*
- * Create an EGL surface we can render into.
- */
- if (surface != null) {
- mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
- } else {
- mEglSurface = null;
- }
-
- if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
- int error = mEgl.eglGetError();
- if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
- Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
- }
- return false;
- }
-
- /*
- * Before we can issue GL commands, we need to make sure
- * the context is current and bound to a surface.
- */
- if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- /*
- * Could not make the context current, probably because the underlying
- * SurfaceView surface has been destroyed.
- */
- logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
- return false;
- }
-
- return true;
- }
-
- /**
- * Create a GL object for the current EGL context.
- */
- public GL10 createGL() {
- return (GL10) mEglContext.getGL();
- }
-
- /**
- * Display the current render surface.
- * @return the EGL error code from eglSwapBuffers.
- */
- public int swap() {
- if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
- return mEgl.eglGetError();
- }
- return EGL10.EGL_SUCCESS;
- }
-
- public void destroySurface() {
- destroySurfaceImp();
- }
-
- private void destroySurfaceImp() {
- if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
- mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
- EGL10.EGL_NO_SURFACE,
- EGL10.EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
- mEglSurface = null;
- }
- }
-
- public void finish() {
- if (mEglContext != null) {
- mEgl.eglDestroyContext(mEglDisplay, mEglContext);
- mEglContext = null;
- }
- if (mEglDisplay != null) {
- mEgl.eglTerminate(mEglDisplay);
- mEglDisplay = null;
- }
- }
-
- private void throwEglException(String function) {
- throwEglException(function, mEgl.eglGetError());
- }
-
- public static void throwEglException(String function, int error) {
- String message = formatEglError(function, error);
- throw new RuntimeException(message);
- }
-
- public static void logEglErrorAsWarning(String tag, String function, int error) {
- Log.w(tag, formatEglError(function, error));
- }
-
- public static String formatEglError(String function, int error) {
- return function + " failed: " + error;
- }
-
- }
-
- private static class RenderThread extends Thread {
- private static final int INVALID = -1;
- private static final int RENDER = 1;
- private static final int CHANGE_SURFACE = 2;
- private static final int RESIZE_SURFACE = 3;
- private static final int FINISH = 4;
-
- private EglHelper mEglHelper = new EglHelper();
-
- private Object mLock = new Object();
- private int mExecMsgId = INVALID;
- private SurfaceTexture mSurface;
- private Renderer mRenderer;
- private int mWidth, mHeight;
-
- private boolean mFinished = false;
- private GL10 mGL;
-
- public RenderThread(Renderer renderer) {
- super("RenderThread");
- mRenderer = renderer;
- start();
- }
-
- private void checkRenderer() {
- if (mRenderer == null) {
- throw new IllegalArgumentException("Renderer is null!");
- }
- }
-
- private void checkSurface() {
- if (mSurface == null) {
- throw new IllegalArgumentException("surface is null!");
- }
- }
-
- public void setSurface(SurfaceTexture surface) {
- // If the surface is null we're being torn down, don't need a
- // renderer then
- if (surface != null) {
- checkRenderer();
- }
- mSurface = surface;
- exec(CHANGE_SURFACE);
- }
-
- public void setSize(int width, int height) {
- checkRenderer();
- checkSurface();
- mWidth = width;
- mHeight = height;
- exec(RESIZE_SURFACE);
- }
-
- public void render() {
- checkRenderer();
- if (mSurface != null) {
- exec(RENDER);
- mSurface.updateTexImage();
- }
- }
-
- public void finish() {
- mSurface = null;
- exec(FINISH);
- try {
- join();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
-
- private void exec(int msgid) {
- synchronized (mLock) {
- if (mExecMsgId != INVALID) {
- throw new IllegalArgumentException(
- "Message already set - multithreaded access?");
- }
- mExecMsgId = msgid;
- mLock.notify();
- try {
- mLock.wait();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- }
-
- private void handleMessageLocked(int what) {
- switch (what) {
- case CHANGE_SURFACE:
- if (mEglHelper.createSurface(mSurface)) {
- mGL = mEglHelper.createGL();
- mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
- }
- break;
- case RESIZE_SURFACE:
- mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
- break;
- case RENDER:
- mRenderer.onDrawFrame(mGL);
- mEglHelper.swap();
- break;
- case FINISH:
- mEglHelper.destroySurface();
- mEglHelper.finish();
- mFinished = true;
- break;
- }
- }
-
- @Override
- public void run() {
- synchronized (mLock) {
- mEglHelper.start();
- while (!mFinished) {
- while (mExecMsgId == INVALID) {
- try {
- mLock.wait();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- handleMessageLocked(mExecMsgId);
- mExecMsgId = INVALID;
- mLock.notify();
- }
- mExecMsgId = FINISH;
- }
- }
- }
-}
diff --git a/WallpaperPicker/src/com/android/photos/views/TiledImageRenderer.java b/WallpaperPicker/src/com/android/photos/views/TiledImageRenderer.java
index c4e493b34..e57ce70b9 100644
--- a/WallpaperPicker/src/com/android/photos/views/TiledImageRenderer.java
+++ b/WallpaperPicker/src/com/android/photos/views/TiledImageRenderer.java
@@ -20,11 +20,11 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.support.v4.util.LongSparseArray;
+import android.support.v4.util.Pools.Pool;
+import android.support.v4.util.Pools.SynchronizedPool;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.Pools.Pool;
-import android.util.Pools.SynchronizedPool;
+import android.util.LongSparseArray;
import android.view.View;
import android.view.WindowManager;
@@ -32,6 +32,7 @@ import com.android.gallery3d.common.Utils;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.GLCanvas;
import com.android.gallery3d.glrenderer.UploadedTexture;
+import com.android.launcher3.util.Thunk;
/**
* Handles laying out, decoding, and drawing of tiles in GL
@@ -67,12 +68,12 @@ public class TiledImageRenderer {
private static final int STATE_RECYCLING = 0x20;
private static final int STATE_RECYCLED = 0x40;
- private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64);
+ @Thunk static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64);
// TILE_SIZE must be 2^N
- private int mTileSize;
+ @Thunk int mTileSize;
- private TileSource mModel;
+ @Thunk TileSource mModel;
private BasicTexture mPreview;
protected int mLevelCount; // cache the value of mScaledBitmaps.length
@@ -82,7 +83,7 @@ public class TiledImageRenderer {
// half size of the previous one). If the value is in [0, mLevelCount), we
// use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value
// is mLevelCount
- private int mLevel = 0;
+ @Thunk int mLevel = 0;
private int mOffsetX;
private int mOffsetY;
@@ -96,10 +97,10 @@ public class TiledImageRenderer {
private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
// The following three queue are guarded by mQueueLock
- private final Object mQueueLock = new Object();
+ @Thunk final Object mQueueLock = new Object();
private final TileQueue mRecycledQueue = new TileQueue();
private final TileQueue mUploadQueue = new TileQueue();
- private final TileQueue mDecodeQueue = new TileQueue();
+ @Thunk final TileQueue mDecodeQueue = new TileQueue();
// The width and height of the full-sized bitmap
protected int mImageWidth = SIZE_UNKNOWN;
@@ -489,7 +490,7 @@ public class TiledImageRenderer {
}
}
- private void decodeTile(Tile tile) {
+ @Thunk void decodeTile(Tile tile) {
synchronized (mQueueLock) {
if (tile.mTileState != STATE_IN_QUEUE) {
return;
@@ -556,7 +557,7 @@ public class TiledImageRenderer {
mActiveTiles.put(key, tile);
}
- private Tile getTile(int x, int y, int level) {
+ @Thunk Tile getTile(int x, int y, int level) {
return mActiveTiles.get(makeTileKey(x, y, level));
}
@@ -748,7 +749,7 @@ public class TiledImageRenderer {
}
}
- private static class TileQueue {
+ @Thunk static class TileQueue {
private Tile mHead;
public Tile pop() {
@@ -786,7 +787,7 @@ public class TiledImageRenderer {
}
}
- private class TileDecoder extends Thread {
+ @Thunk class TileDecoder extends Thread {
public void finishAndWait() {
interrupt();
diff --git a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
index 94063b027..7e3e1a936 100644
--- a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
+++ b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
@@ -16,8 +16,6 @@
package com.android.photos.views;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -28,35 +26,26 @@ import android.graphics.Paint.Align;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
-import android.os.Build;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
-import android.view.View;
import android.widget.FrameLayout;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.launcher3.util.Thunk;
import com.android.photos.views.TiledImageRenderer.TileSource;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
- * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
- * or {@link BlockingGLTextureView}.
+ * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}.
*/
public class TiledImageView extends FrameLayout {
- private static final boolean USE_TEXTURE_VIEW = false;
- private static final boolean IS_SUPPORTED =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
- private static final boolean USE_CHOREOGRAPHER =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-
- private BlockingGLTextureView mTextureView;
- private GLSurfaceView mGLSurfaceView;
- private boolean mInvalPending = false;
+ @Thunk GLSurfaceView mGLSurfaceView;
+ @Thunk boolean mInvalPending = false;
private FrameCallback mFrameCallback;
protected static class ImageRendererWrapper {
@@ -79,35 +68,19 @@ public class TiledImageView extends FrameLayout {
protected Object mLock = new Object();
protected ImageRendererWrapper mRenderer;
- public static boolean isTilingSupported() {
- return IS_SUPPORTED;
- }
-
public TiledImageView(Context context) {
this(context, null);
}
public TiledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
- if (!IS_SUPPORTED) {
- return;
- }
-
mRenderer = new ImageRendererWrapper();
mRenderer.image = new TiledImageRenderer(this);
- View view;
- if (USE_TEXTURE_VIEW) {
- mTextureView = new BlockingGLTextureView(context);
- mTextureView.setRenderer(new TileRenderer());
- view = mTextureView;
- } else {
- mGLSurfaceView = new GLSurfaceView(context);
- mGLSurfaceView.setEGLContextClientVersion(2);
- mGLSurfaceView.setRenderer(new TileRenderer());
- mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- view = mGLSurfaceView;
- }
- addView(view, new LayoutParams(
+ mGLSurfaceView = new GLSurfaceView(context);
+ mGLSurfaceView.setEGLContextClientVersion(2);
+ mGLSurfaceView.setRenderer(new TileRenderer());
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ addView(mGLSurfaceView, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
//setTileSource(new ColoredTiles());
}
@@ -117,22 +90,11 @@ public class TiledImageView extends FrameLayout {
super.setVisibility(visibility);
// need to update inner view's visibility because it seems like we're causing it to draw
// from {@link #dispatchDraw} or {@link #invalidate} even if we are invisible.
- if (USE_TEXTURE_VIEW) {
- mTextureView.setVisibility(visibility);
- } else {
- mGLSurfaceView.setVisibility(visibility);
- }
+ mGLSurfaceView.setVisibility(visibility);
}
public void destroy() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (USE_TEXTURE_VIEW) {
- mTextureView.destroy();
- } else {
- mGLSurfaceView.queueEvent(mFreeTextures);
- }
+ mGLSurfaceView.queueEvent(mFreeTextures);
}
private Runnable mFreeTextures = new Runnable() {
@@ -144,27 +106,14 @@ public class TiledImageView extends FrameLayout {
};
public void onPause() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (!USE_TEXTURE_VIEW) {
- mGLSurfaceView.onPause();
- }
+ mGLSurfaceView.onPause();
}
public void onResume() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (!USE_TEXTURE_VIEW) {
- mGLSurfaceView.onResume();
- }
+ mGLSurfaceView.onResume();
}
public void setTileSource(TileSource source, Runnable isReadyCallback) {
- if (!IS_SUPPORTED) {
- return;
- }
synchronized (mLock) {
mRenderer.source = source;
mRenderer.isReadyCallback = isReadyCallback;
@@ -177,13 +126,14 @@ public class TiledImageView extends FrameLayout {
invalidate();
}
+ public TileSource getTileSource() {
+ return mRenderer.source;
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if (!IS_SUPPORTED) {
- return;
- }
synchronized (mLock) {
updateScaleIfNecessaryLocked(mRenderer);
}
@@ -200,43 +150,10 @@ public class TiledImageView extends FrameLayout {
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- if (!IS_SUPPORTED) {
- return;
- }
- if (USE_TEXTURE_VIEW) {
- mTextureView.render();
- }
- super.dispatchDraw(canvas);
- }
-
- @SuppressLint("NewApi")
- @Override
- public void setTranslationX(float translationX) {
- if (!IS_SUPPORTED) {
- return;
- }
- super.setTranslationX(translationX);
- }
-
- @Override
public void invalidate() {
- if (!IS_SUPPORTED) {
- return;
- }
- if (USE_TEXTURE_VIEW) {
- super.invalidate();
- mTextureView.invalidate();
- } else {
- if (USE_CHOREOGRAPHER) {
- invalOnVsync();
- } else {
- mGLSurfaceView.requestRender();
- }
- }
+ invalOnVsync();
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void invalOnVsync() {
if (!mInvalPending) {
mInvalPending = true;
@@ -255,9 +172,6 @@ public class TiledImageView extends FrameLayout {
private RectF mTempRectF = new RectF();
public void positionFromMatrix(Matrix matrix) {
- if (!IS_SUPPORTED) {
- return;
- }
if (mRenderer.source != null) {
final int rotation = mRenderer.source.getRotation();
final boolean swap = !(rotation % 180 == 0);
@@ -290,7 +204,7 @@ public class TiledImageView extends FrameLayout {
}
}
- private class TileRenderer implements Renderer {
+ @Thunk class TileRenderer implements Renderer {
private GLES20Canvas mCanvas;
diff --git a/proguard.flags b/proguard.flags
index 83a491dfd..e2a4b5b31 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -6,7 +6,6 @@
public void onClickVoiceButton(android.view.View);
public void onClickConfigureButton(android.view.View);
public void onClickAllAppsButton(android.view.View);
- public void onClickAppMarketButton(android.view.View);
public void dismissFirstRunCling(android.view.View);
public void dismissMigrationClingCopyApps(android.view.View);
public void dismissMigrationClingUseDefault(android.view.View);
@@ -62,3 +61,8 @@
public int getBrightness();
public void setBrightness(int);
}
+
+-keep class com.android.launcher3.AppsContainerRecyclerView {
+ public void setFastScrollerAlpha(float);
+ public float getFastScrollerAlpha();
+} \ No newline at end of file
diff --git a/protos/backup.proto b/protos/backup.proto
index 44c4b09e3..09330ee06 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -72,6 +72,17 @@ message Journal {
}
message Favorite {
+ // Type of the app, this target represents
+ enum TargetType {
+ TARGET_NONE = 0;
+ TARGET_PHONE = 1;
+ TARGET_MESSENGER = 2;
+ TARGET_EMAIL = 3;
+ TARGET_BROWSER = 4;
+ TARGET_GALLERY = 5;
+ TARGET_CAMERA = 6;
+ }
+
required int64 id = 1;
required int32 itemType = 2;
optional string title = 3;
@@ -90,6 +101,7 @@ message Favorite {
optional string iconPackage = 16;
optional string iconResource = 17;
optional bytes icon = 18;
+ optional TargetType targetType = 19 [default = TARGET_NONE];
}
message Screen {
diff --git a/res/drawable-hdpi/ic_launcher_market_holo.png b/res/drawable-hdpi/ic_launcher_market_holo.png
deleted file mode 100644
index dc7825114..000000000
--- a/res/drawable-hdpi/ic_launcher_market_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current.png b/res/drawable-hdpi/ic_pageindicator_current.png
index 283f44d37..423ca2b45 100644
--- a/res/drawable-hdpi/ic_pageindicator_current.png
+++ b/res/drawable-hdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current_folder.png b/res/drawable-hdpi/ic_pageindicator_current_folder.png
new file mode 100644
index 000000000..43fbb0e79
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default.png b/res/drawable-hdpi/ic_pageindicator_default.png
index 47b998967..83fa73fc6 100644
--- a/res/drawable-hdpi/ic_pageindicator_default.png
+++ b/res/drawable-hdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default_folder.png b/res/drawable-hdpi/ic_pageindicator_default_folder.png
new file mode 100644
index 000000000..55cab1c65
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-ldrtl/apps_list_fastscroll_bg.xml b/res/drawable-ldrtl/apps_list_fastscroll_bg.xml
new file mode 100644
index 000000000..772975a71
--- /dev/null
+++ b/res/drawable-ldrtl/apps_list_fastscroll_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/apps_view_scrollbar_thumb_color" />
+ <size
+ android:width="64dp"
+ android:height="64dp" />
+ <corners
+ android:topLeftRadius="64dp"
+ android:topRightRadius="64dp"
+ android:bottomRightRadius="64dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable-mdpi/ic_launcher_market_holo.png b/res/drawable-mdpi/ic_launcher_market_holo.png
deleted file mode 100644
index cacb37484..000000000
--- a/res/drawable-mdpi/ic_launcher_market_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current.png b/res/drawable-mdpi/ic_pageindicator_current.png
index b41e1bb67..ca889c4d1 100644
--- a/res/drawable-mdpi/ic_pageindicator_current.png
+++ b/res/drawable-mdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current_folder.png b/res/drawable-mdpi/ic_pageindicator_current_folder.png
new file mode 100644
index 000000000..5bbba9140
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default.png b/res/drawable-mdpi/ic_pageindicator_default.png
index e36c25cf1..34493b155 100644
--- a/res/drawable-mdpi/ic_pageindicator_default.png
+++ b/res/drawable-mdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default_folder.png b/res/drawable-mdpi/ic_pageindicator_default_folder.png
new file mode 100644
index 000000000..0a987a4d0
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_market_holo.png b/res/drawable-xhdpi/ic_launcher_market_holo.png
deleted file mode 100644
index 958f0de3c..000000000
--- a/res/drawable-xhdpi/ic_launcher_market_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current.png b/res/drawable-xhdpi/ic_pageindicator_current.png
index 8fa774dee..3054f2f4f 100644
--- a/res/drawable-xhdpi/ic_pageindicator_current.png
+++ b/res/drawable-xhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current_folder.png b/res/drawable-xhdpi/ic_pageindicator_current_folder.png
new file mode 100644
index 000000000..cd92e9f21
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default.png b/res/drawable-xhdpi/ic_pageindicator_default.png
index 8eb5eb08d..38538dcf0 100644
--- a/res/drawable-xhdpi/ic_pageindicator_default.png
+++ b/res/drawable-xhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default_folder.png b/res/drawable-xhdpi/ic_pageindicator_default_folder.png
new file mode 100644
index 000000000..e7c46e3a1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current.png b/res/drawable-xxhdpi/ic_pageindicator_current.png
index 22b290e69..5941c8e4f 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_current.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current_folder.png b/res/drawable-xxhdpi/ic_pageindicator_current_folder.png
new file mode 100644
index 000000000..602b89a40
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_current_folder.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default.png b/res/drawable-xxhdpi/ic_pageindicator_default.png
index e608cae30..3fa9e5fd7 100644
--- a/res/drawable-xxhdpi/ic_pageindicator_default.png
+++ b/res/drawable-xxhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default_folder.png b/res/drawable-xxhdpi/ic_pageindicator_default_folder.png
new file mode 100644
index 000000000..bbcd7f91e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_default_folder.png
Binary files differ
diff --git a/res/drawable/apps_list_bg.xml b/res/drawable/apps_list_bg.xml
new file mode 100644
index 000000000..0e56684b5
--- /dev/null
+++ b/res/drawable/apps_list_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffff" />
+ <corners
+ android:radius="2dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/apps_list_fastscroll_bg.xml b/res/drawable/apps_list_fastscroll_bg.xml
new file mode 100644
index 000000000..780d3b0c3
--- /dev/null
+++ b/res/drawable/apps_list_fastscroll_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/apps_view_scrollbar_thumb_color" />
+ <size
+ android:width="64dp"
+ android:height="64dp" />
+ <corners
+ android:topLeftRadius="64dp"
+ android:topRightRadius="64dp"
+ android:bottomLeftRadius="64dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/apps_list_scrollbar_thumb.xml b/res/drawable/apps_list_scrollbar_thumb.xml
new file mode 100644
index 000000000..318d40678
--- /dev/null
+++ b/res/drawable/apps_list_scrollbar_thumb.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/apps_view_scrollbar_thumb_color" />
+ <size android:width="@dimen/apps_view_fast_scroll_bar_width" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/apps_list_search_bg.xml b/res/drawable/apps_list_search_bg.xml
new file mode 100644
index 000000000..63c4d5591
--- /dev/null
+++ b/res/drawable/apps_list_search_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffff" />
+ <corners
+ android:bottomLeftRadius="2dp"
+ android:bottomRightRadius="2dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/apps_reveal_bg.xml b/res/drawable/apps_reveal_bg.xml
new file mode 100644
index 000000000..07505a596
--- /dev/null
+++ b/res/drawable/apps_reveal_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffff" />
+ <corners android:radius="2dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/apps_search_bg.xml b/res/drawable/apps_search_bg.xml
new file mode 100644
index 000000000..405e8447c
--- /dev/null
+++ b/res/drawable/apps_search_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffff" />
+ <corners
+ android:topLeftRadius="2dp"
+ android:topRightRadius="2dp" />
+</shape> \ No newline at end of file
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 6f95bd506..d5dd91ab4 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -57,8 +57,14 @@
android:id="@+id/overview_panel"
android:visibility="gone" />
- <include layout="@layout/apps_customize_pane"
- android:id="@+id/apps_customize_pane"
+ <include layout="@layout/widgets_view"
+ android:id="@+id/widgets_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
+ <include layout="@layout/apps_view"
+ android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index af30a32e5..5a018c516 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -66,8 +66,14 @@
android:id="@+id/search_drop_target_bar"
layout="@layout/search_drop_target_bar" />
- <include layout="@layout/apps_customize_pane"
- android:id="@+id/apps_customize_pane"
+ <include layout="@layout/widgets_view"
+ android:id="@+id/widgets_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
+ <include layout="@layout/apps_view"
+ android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
diff --git a/res/layout-sw600dp/apps_view.xml b/res/layout-sw600dp/apps_view.xml
new file mode 100644
index 000000000..e6e0ec358
--- /dev/null
+++ b/res/layout-sw600dp/apps_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.AppsContainerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/apps_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/apps_container_inset"
+ android:descendantFocusability="afterDescendants">
+ <include
+ layout="@layout/apps_reveal_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+ <include
+ layout="@layout/apps_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+</com.android.launcher3.AppsContainerView> \ No newline at end of file
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 960ccf330..a9601af6a 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -66,8 +66,14 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
- <include layout="@layout/apps_customize_pane"
- android:id="@+id/apps_customize_pane"
+ <include layout="@layout/widgets_view"
+ android:id="@+id/widgets_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+
+ <include layout="@layout/apps_view"
+ android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
deleted file mode 100644
index e42576ffe..000000000
--- a/res/layout/apps_customize_pane.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.launcher3.AppsCustomizeTabHost
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:clipChildren="false">
-
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:orientation="vertical">
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:clipChildren="false">
- <FrameLayout
- android:id="@+id/fake_page_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false">
- <FrameLayout
- android:id="@+id/fake_page"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"
- android:clipToPadding="false" />
- </FrameLayout>
- <com.android.launcher3.AppsCustomizePagedView
- android:id="@+id/apps_customize_pane_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
- launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
- launcher:maxGap="@dimen/workspace_max_gap"
- launcher:pageIndicator="@+id/apps_customize_page_indicator" />
- </FrameLayout>
- <include
- android:id="@+id/apps_customize_page_indicator"
- layout="@layout/page_indicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
- </LinearLayout>
-</com.android.launcher3.AppsCustomizeTabHost>
diff --git a/res/layout/apps_empty_view.xml b/res/layout/apps_empty_view.xml
new file mode 100644
index 000000000..8408077a2
--- /dev/null
+++ b/res/layout/apps_empty_view.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/empty_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:paddingTop="24dp"
+ android:paddingBottom="24dp"
+ android:paddingRight="@dimen/apps_grid_view_start_margin"
+ android:textSize="16sp"
+ android:textColor="#4c4c4c"
+ android:focusable="false" />
+
diff --git a/res/layout/apps_grid_row_icon_view.xml b/res/layout/apps_grid_row_icon_view.xml
new file mode 100644
index 000000000..81e74b985
--- /dev/null
+++ b/res/layout/apps_grid_row_icon_view.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.BubbleTextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ style="@style/WorkspaceIcon.AppsCustomize"
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left|center_vertical"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg"
+ launcher:deferShadowGeneration="true" />
+
diff --git a/res/layout/apps_list_row_icon_view.xml b/res/layout/apps_list_row_icon_view.xml
new file mode 100644
index 000000000..867dbdc99
--- /dev/null
+++ b/res/layout/apps_list_row_icon_view.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.BubbleTextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ style="@style/WorkspaceIcon.AppsCustomize"
+ android:id="@+id/application_icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg"
+ launcher:iconPaddingOverride="24dp"
+ launcher:textSizeOverride="16dp"
+ launcher:layoutHorizontal="true"
+ launcher:deferShadowGeneration="true" />
diff --git a/res/layout/apps_list_row_view.xml b/res/layout/apps_list_row_view.xml
new file mode 100644
index 000000000..e80285b95
--- /dev/null
+++ b/res/layout/apps_list_row_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/apps_view_row_height"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg"
+ android:descendantFocusability="afterDescendants">
+ <TextView
+ android:id="@+id/section"
+ android:layout_width="64dp"
+ android:layout_height="match_parent"
+ android:paddingLeft="16dp"
+ android:gravity="start|center_vertical"
+ android:textColor="@color/apps_view_section_text_color"
+ android:textSize="@dimen/apps_view_section_text_size"
+ android:focusable="false" />
+</LinearLayout>
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
new file mode 100644
index 000000000..dfb7b588d
--- /dev/null
+++ b/res/layout/apps_list_view.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/apps_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:elevation="15dp"
+ android:visibility="gone"
+ android:focusableInTouchMode="true">
+ <EditText
+ android:id="@+id/app_search_box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:hint="@string/apps_view_search_bar_hint"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:gravity="fill_horizontal"
+ android:textSize="16sp"
+ android:textColor="#4c4c4c"
+ android:textColorHint="#9c9c9c"
+ android:imeOptions="actionDone|flagNoExtractUi"
+ android:background="@drawable/apps_search_bg"
+ android:elevation="4dp" />
+ <com.android.launcher3.AppsContainerRecyclerView
+ android:id="@+id/apps_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:descendantFocusability="afterDescendants" />
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/apps_reveal_view.xml b/res/layout/apps_reveal_view.xml
new file mode 100644
index 000000000..2951ea4f4
--- /dev/null
+++ b/res/layout/apps_reveal_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/apps_view_transition_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:elevation="15dp"
+ android:visibility="invisible"
+ android:focusable="false" /> \ No newline at end of file
diff --git a/res/layout/apps_view.xml b/res/layout/apps_view.xml
new file mode 100644
index 000000000..7f09f7795
--- /dev/null
+++ b/res/layout/apps_view.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<!-- The top and bottom paddings are defined in this container, but since we want
+ the list view to span the full width (for touch interception purposes), we
+ will bake the left/right padding into that view's background itself. -->
+<com.android.launcher3.AppsContainerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/apps_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:descendantFocusability="afterDescendants">
+ <include
+ layout="@layout/apps_reveal_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+ <include
+ layout="@layout/apps_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+</com.android.launcher3.AppsContainerView> \ No newline at end of file
diff --git a/res/layout/page_indicator_marker.xml b/res/layout/page_indicator_marker.xml
index 686d27569..564a95811 100644
--- a/res/layout/page_indicator_marker.xml
+++ b/res/layout/page_indicator_marker.xml
@@ -16,8 +16,8 @@
<com.android.launcher3.PageIndicatorMarker
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:layout_width="16dp"
- android:layout_height="16dp"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/inactive"
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml
index 0d7167e5b..9b0da1d4e 100644
--- a/res/layout/search_drop_target_bar.xml
+++ b/res/layout/search_drop_target_bar.xml
@@ -45,6 +45,19 @@
style="@style/DropTargetButtonContainer"
android:layout_weight="1" >
+ <!-- Uninstall target -->
+
+ <com.android.launcher3.UninstallDropTarget
+ android:id="@+id/uninstall_target_text"
+ style="@style/DropTargetButton"
+ android:drawableStart="@drawable/uninstall_target_selector"
+ android:text="@string/delete_target_uninstall_label" />
+ </FrameLayout>
+
+ <FrameLayout
+ style="@style/DropTargetButtonContainer"
+ android:layout_weight="1" >
+
<!-- Info target -->
<com.android.launcher3.InfoDropTarget
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ed8d43e46..cd3a051ad 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!--
+ Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,42 +15,63 @@
limitations under the License.
-->
-<com.android.launcher3.Folder
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.Folder xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/quantum_panel">
+ android:background="@drawable/quantum_panel"
+ android:orientation="vertical" >
- <ScrollView
- android:id="@+id/scroll_view"
+ <FrameLayout
+ android:id="@+id/folder_content_wrapper"
android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.android.launcher3.CellLayout
- android:id="@+id/folder_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:cacheColorHint="#ff333333"
- android:hapticFeedbackEnabled="false" />
- </ScrollView>
-
- <com.android.launcher3.FolderEditText
- android:id="@+id/folder_name"
+ android:layout_height="match_parent" >
+
+ <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size -->
+
+ <com.android.launcher3.FocusIndicatorView
+ android:id="@+id/focus_indicator"
+ android:layout_width="20dp"
+ android:layout_height="20dp" />
+
+ <com.android.launcher3.FolderPagedView
+ android:id="@+id/folder_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:pageIndicator="@+id/folder_page_indicator" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/folder_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:paddingTop="@dimen/folder_name_padding"
- android:paddingBottom="@dimen/folder_name_padding"
- android:background="#00000000"
- android:hint="@string/folder_hint_text"
- android:textSize="14sp"
- android:textColor="#ff777777"
- android:textColorHint="#ff808080"
- android:textColorHighlight="#ffCCCCCC"
- android:textCursorDrawable="@null"
- android:gravity="center_horizontal"
- android:singleLine="true"
- android:imeOptions="flagNoExtractUi"
- android:fontFamily="sans-serif-condensed"/>
-</com.android.launcher3.Folder>
+ android:orientation="vertical" >
+
+ <include
+ android:id="@+id/folder_page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="12dp"
+ android:layout_gravity="center_horizontal"
+ layout="@layout/page_indicator" />
+
+ <com.android.launcher3.FolderEditText
+ android:id="@+id/folder_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:background="#00000000"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="center_horizontal"
+ android:hint="@string/folder_hint_text"
+ android:imeOptions="flagNoExtractUi"
+ android:paddingBottom="@dimen/folder_name_padding"
+ android:paddingTop="@dimen/folder_name_padding"
+ android:singleLine="true"
+ android:textColor="#ff777777"
+ android:textColorHighlight="#ffCCCCCC"
+ android:textColorHint="#ff808080"
+ android:textCursorDrawable="@null"
+ android:textSize="14sp" />
+ </LinearLayout>
+
+</com.android.launcher3.Folder> \ No newline at end of file
diff --git a/res/layout/apps_customize_widget.xml b/res/layout/widget_cell.xml
index a8344e3ff..f53b74ef8 100644
--- a/res/layout/apps_customize_widget.xml
+++ b/res/layout/widget_cell.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2015 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.
@@ -13,58 +13,26 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.PagedViewWidget
+<com.android.launcher3.widget.WidgetCell
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
-
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="@dimen/widget_preview_container_width"
+ android:layout_height="wrap_content"
android:layout_weight="1"
+ android:paddingTop="@dimen/widget_preview_padding_top"
android:orientation="vertical"
-
android:background="@drawable/focusable_view_bg"
android:focusable="true">
<LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1">
- <FrameLayout
- android:id="@+id/left_border"
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="@color/widget_text_panel"
- android:visibility="gone" />
-
- <!-- The preview of the widget or shortcut. -->
- <com.android.launcher3.PagedViewWidgetImageView
- android:id="@+id/widget_preview"
- style="@style/PagedViewWidgetImageView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:paddingTop="@dimen/app_widget_preview_padding_top"
- android:paddingEnd="@dimen/app_widget_preview_padding_right"
- android:paddingRight="@dimen/app_widget_preview_padding_right"
- android:scaleType="matrix" />
- <FrameLayout
- android:id="@+id/right_border"
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="@color/widget_text_panel"
- android:visibility="gone" />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingTop="@dimen/app_widget_preview_label_vertical_padding"
- android:paddingBottom="@dimen/app_widget_preview_label_vertical_padding"
- android:paddingLeft="@dimen/app_widget_preview_label_horizontal_padding"
- android:paddingRight="@dimen/app_widget_preview_label_horizontal_padding"
- android:background="@color/widget_text_panel"
+ android:paddingTop="@dimen/widget_preview_label_vertical_padding"
+ android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
+ android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
+ android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
android:orientation="horizontal">
+
<!-- The name of the widget. -->
<TextView
android:id="@+id/widget_name"
@@ -73,7 +41,7 @@
android:layout_weight="1"
android:gravity="start"
android:singleLine="true"
- android:ellipsize="marquee"
+ android:ellipsize="end"
android:fadingEdge="horizontal"
android:textColor="#FFFFFFFF"
@@ -89,17 +57,26 @@
android:id="@+id/widget_dims"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
android:layout_marginStart="5dp"
+ android:layout_marginLeft="5dp"
android:layout_weight="0"
android:gravity="start"
android:textColor="#FFFFFFFF"
android:textSize="12sp"
+ android:textAlignment="viewStart"
android:fontFamily="sans-serif-condensed"
android:shadowRadius="2.0"
android:shadowColor="#B0000000" />
</LinearLayout>
+ <!-- The image of the widget. -->
+ <com.android.launcher3.widget.WidgetImageView
+ android:id="@+id/widget_preview"
+ style="@style/WidgetImageView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:scaleType="matrix" />
-</com.android.launcher3.PagedViewWidget>
+</com.android.launcher3.widget.WidgetCell>
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
new file mode 100644
index 000000000..017b45066
--- /dev/null
+++ b/res/layout/widgets_list_row_view.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widgets_cell_list_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg"
+ android:descendantFocusability="afterDescendants">
+
+ <!-- Section info -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg"
+ android:descendantFocusability="afterDescendants">
+ <ImageView
+ android:id="@+id/section_image"
+ android:layout_width="@dimen/widget_section_height"
+ android:layout_height="@dimen/widget_section_height"
+ android:paddingLeft="@dimen/widget_section_icon_padding"
+ android:paddingRight="@dimen/widget_section_icon_padding"
+ android:paddingTop="@dimen/widget_section_icon_padding"
+ android:paddingBottom="@dimen/widget_section_icon_padding"
+ android:background="@color/widget_text_panel"
+ />
+ <TextView
+ android:id="@+id/section"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/widget_section_height"
+ android:paddingTop="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:gravity="start|center_vertical"
+ android:textColor="@color/widgets_view_section_text_color"
+ android:background="@color/widget_text_panel"
+ android:textSize="20sp"
+ android:focusable="false" />
+ </LinearLayout>
+
+ <!-- Widget list -->
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ >
+ <!-- TODO(hyunyoungs): replace the indicator with actual assets. -->
+ <FrameLayout
+ android:id="@+id/scrollable_indicator"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/ic_pageindicator_default"
+ android:visibility="invisible"
+ />
+ <HorizontalScrollView
+ android:id="@+id/widgets_scroll_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/widget_cell_height"
+ android:scrollbars="none" >
+ <LinearLayout
+ android:id="@+id/widgets_cell_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" />
+ </HorizontalScrollView>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
new file mode 100644
index 000000000..0800f59aa
--- /dev/null
+++ b/res/layout/widgets_view.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<!-- The top and bottom paddings are defined in this container, but since we want
+ the list view to span the full width (for touch interception purposes), we
+ will bake the left/right padding into that view's background itself. -->
+<com.android.launcher3.widget.WidgetsContainerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widgets_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/widget_container_inset"
+ android:paddingBottom="@dimen/widget_container_inset"
+ android:descendantFocusability="afterDescendants">
+
+ <FrameLayout
+ android:id="@+id/widgets_reveal_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:visibility="invisible"
+ android:focusable="false" />
+
+ <LinearLayout
+ android:id="@+id/widgets_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/widgets_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/quantum_panel_dark"/>
+ </LinearLayout>
+</com.android.launcher3.widget.WidgetsContainerView> \ No newline at end of file
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 2015158d9..2d3c633c3 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -199,10 +199,6 @@
<skip />
<!-- no translation found for folder_name_format (6629239338071103179) -->
<skip />
- <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
- <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
- <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
- <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
<!-- no translation found for widget_button_text (2880537293434387943) -->
<skip />
<!-- no translation found for wallpaper_button_text (8404103075899945851) -->
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 3b0deb85b..a4955ceda 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -23,13 +23,11 @@
<string name="home" msgid="5921706419368316758">"Kodu"</string>
<string name="uid_name" msgid="3371120195364560632">"Androidi tuumrakendused"</string>
<string name="folder_name" msgid="8551881338202938211"></string>
- <string name="chooser_wallpaper" msgid="6063168087625352235">"Taustapildi valimiskoht:"</string>
<string name="wallpaper_instructions" msgid="4215640646180727542">"Määra taustapilt"</string>
<string name="pick_wallpaper" msgid="5630222540525626723">"Taustapildid"</string>
<string name="activity_not_found" msgid="217823393239365967">"Rakendus pole installitud."</string>
<string name="widgets_tab_label" msgid="9145860100000983599">"Vidinad"</string>
<string name="long_press_widget_to_add" msgid="7395697462851217506">"Vidina valimiseks puudutage seda pikalt."</string>
- <string name="market" msgid="2652226429823445833">"Pood"</string>
<string name="widget_dims_format" msgid="1386418557719032947">"%1$d × %2$d"</string>
<string name="external_drop_widget_error" msgid="2285187188524172774">"Ãœksust ei saa sellele avaekraanile tuua."</string>
<string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Valige loomiseks vidin"</string>
@@ -52,23 +50,13 @@
<string name="title_select_application" msgid="1793455815754848652">"Rakenduse valimine"</string>
<string name="all_apps_button_label" msgid="2578400570124163469">"Rakendused"</string>
<string name="all_apps_home_button_label" msgid="1022222300329398558">"Kodu"</string>
- <string name="delete_zone_label_workspace" msgid="7153615831493049150">"Eemalda"</string>
<string name="delete_zone_label_all_apps" msgid="6664588234817475108">"Desinstalli"</string>
<string name="delete_target_label" msgid="665300185123139530">"Eemalda"</string>
<string name="delete_target_uninstall_label" msgid="748894921183769150">"Desinstalli"</string>
<string name="info_target_label" msgid="4019495079517426980">"Rakenduse teave"</string>
- <string name="accessibility_search_button" msgid="816822994629942611">"Otsing"</string>
- <string name="accessibility_voice_search_button" msgid="3938249215065842475">"Häälotsing"</string>
<string name="accessibility_all_apps_button" msgid="8803738611398979849">"Rakendused"</string>
<string name="accessibility_delete_button" msgid="3628162007991023603">"Eemalda"</string>
<string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"Desinstalli värskendus"</string>
- <string name="menu_add" msgid="3065046628354640854">"Lisa"</string>
- <string name="menu_manage_apps" msgid="2308685199463588895">"Rakenduste haldamine"</string>
- <string name="menu_wallpaper" msgid="5837429080911269832">"Taustapilt"</string>
- <string name="menu_search" msgid="4826514464423239041">"Otsing"</string>
- <string name="menu_notifications" msgid="6424587053194766192">"Teadistused"</string>
- <string name="menu_settings" msgid="3946232973327980394">"Süsteemiseaded"</string>
- <string name="menu_help" msgid="4901160661634590633">"Abi"</string>
<string name="cab_menu_delete_app" msgid="4089398025537640349">"Rakenduse desinstallimine"</string>
<string name="cab_menu_app_info" msgid="914548323652698884">"Rakenduse üksikasjad"</string>
<string name="cab_app_selection_text" msgid="6378522164293415735">"Valitud on 1 rakendus"</string>
@@ -94,11 +82,7 @@
<string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"Vidinate leht %1$d/%2$d"</string>
<string name="workspace_cling_title" msgid="738396473989890567">"Tunne end nagu kodus"</string>
<string name="workspace_cling_move_item" msgid="791013895761065070">"Võite panna oma lemmikrakendused siia."</string>
- <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Kõikide oma rakenduste nägemiseks puudutage ringi."</string>
- <string name="all_apps_cling_title" msgid="2559734712581447107">"Valige mõned rakendused"</string>
- <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Rakenduse lisamiseks avakuvale puudutage seda pikalt."</string>
<string name="folder_cling_title" msgid="4308949882377840953">"Korraldage oma rakendused kaustadesse"</string>
- <string name="folder_cling_move_item" msgid="270598675060435169">"Rakenduse liigutamiseks pange sõrm rakendusele ja hoidke seda."</string>
<string name="folder_cling_create_folder" msgid="8352867485656129478">"Avakuval uue kausta tegemiseks virnastage üks rakendus teisele."</string>
<string name="cling_dismiss" msgid="2780907108735868381">"OK"</string>
<string name="folder_opened" msgid="1262064100943801533">"Kaust on avatud, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
@@ -107,8 +91,4 @@
<string name="folder_closed" msgid="3130534551370511932">"Kaust suletud"</string>
<string name="folder_renamed" msgid="7951233572858053642">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="3051680259794759037">"<xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="custom_workspace_cling_title_1" msgid="1433009175359948587"></string>
- <string name="custom_workspace_cling_description_1" msgid="6875529190849858047"></string>
- <string name="custom_workspace_cling_title_2" msgid="5516006164661020362"></string>
- <string name="custom_workspace_cling_description_2" msgid="2758258454975288377"></string>
</resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 1b3418154..06a99842e 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -18,9 +18,4 @@
<!-- QSB -->
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
<dimen name="toolbar_button_horizontal_padding">0dip</dimen>
-
-<!-- AppsCustomize -->
- <dimen name="apps_customize_tab_bar_height">42dp</dimen>
- <integer name="apps_customize_widget_cell_count_x">3</integer>
- <integer name="apps_customize_widget_cell_count_y">2</integer>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index f50a5db5b..248ca1517 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -23,13 +23,11 @@
<string name="home" msgid="5921706419368316758">"Laman Utama"</string>
<string name="uid_name" msgid="3371120195364560632">"Apl Teras Android"</string>
<string name="folder_name" msgid="8551881338202938211"></string>
- <string name="chooser_wallpaper" msgid="6063168087625352235">"Pilih kertas dinding dari"</string>
<string name="wallpaper_instructions" msgid="4215640646180727542">"Tetapkan kertas dinding"</string>
<string name="pick_wallpaper" msgid="5630222540525626723">"Kertas dinding"</string>
<string name="activity_not_found" msgid="217823393239365967">"Aplikasi tidak dipasang."</string>
<string name="widgets_tab_label" msgid="9145860100000983599">"Widget"</string>
<string name="long_press_widget_to_add" msgid="7395697462851217506">"Sentuh &amp; tahan untuk mengambil widget."</string>
- <string name="market" msgid="2652226429823445833">"Kedai"</string>
<string name="widget_dims_format" msgid="1386418557719032947">"%1$d × %2$d"</string>
<string name="external_drop_widget_error" msgid="2285187188524172774">"Tidak dapat melepaskan item pada skrin Utama ini."</string>
<string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Pilih widget untuk dibuat"</string>
@@ -52,23 +50,13 @@
<string name="title_select_application" msgid="1793455815754848652">"Pilih aplikasi"</string>
<string name="all_apps_button_label" msgid="2578400570124163469">"Apl"</string>
<string name="all_apps_home_button_label" msgid="1022222300329398558">"Laman Utama"</string>
- <string name="delete_zone_label_workspace" msgid="7153615831493049150">"Alih keluar"</string>
<string name="delete_zone_label_all_apps" msgid="6664588234817475108">"Nyahpasang"</string>
<string name="delete_target_label" msgid="665300185123139530">"Alih keluar"</string>
<string name="delete_target_uninstall_label" msgid="748894921183769150">"Nyahpasang"</string>
<string name="info_target_label" msgid="4019495079517426980">"Maklumat apl"</string>
- <string name="accessibility_search_button" msgid="816822994629942611">"Carian"</string>
- <string name="accessibility_voice_search_button" msgid="3938249215065842475">"Carian Suara"</string>
<string name="accessibility_all_apps_button" msgid="8803738611398979849">"Aplikasi"</string>
<string name="accessibility_delete_button" msgid="3628162007991023603">"Alih keluar"</string>
<string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"Nyahpasang kemas kini"</string>
- <string name="menu_add" msgid="3065046628354640854">"Tambah"</string>
- <string name="menu_manage_apps" msgid="2308685199463588895">"Mengurus apl"</string>
- <string name="menu_wallpaper" msgid="5837429080911269832">"Kertas dinding"</string>
- <string name="menu_search" msgid="4826514464423239041">"Cari"</string>
- <string name="menu_notifications" msgid="6424587053194766192">"Pemberitahuan"</string>
- <string name="menu_settings" msgid="3946232973327980394">"Tetapan sistem"</string>
- <string name="menu_help" msgid="4901160661634590633">"Bantuan"</string>
<string name="cab_menu_delete_app" msgid="4089398025537640349">"Nyahpasang aplikasi"</string>
<string name="cab_menu_app_info" msgid="914548323652698884">"Butiran aplikasi"</string>
<string name="cab_app_selection_text" msgid="6378522164293415735">"1 aplikasi dipilih"</string>
@@ -94,11 +82,7 @@
<string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"Halaman widget %1$d dari %2$d"</string>
<string name="workspace_cling_title" msgid="738396473989890567">"Buat diri anda seperti di rumah"</string>
<string name="workspace_cling_move_item" msgid="791013895761065070">"Anda boleh meletakkan aplikasi kegemaran anda di sini."</string>
- <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Untuk melihat semua aplikasi anda, sentuh bulatan."</string>
- <string name="all_apps_cling_title" msgid="2559734712581447107">"Pilih beberapa aplikasi"</string>
- <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Untuk menambahkan aplikasi pada skrin Utama anda, sentuh &amp; tahankannya."</string>
<string name="folder_cling_title" msgid="4308949882377840953">"Susun aplikasi anda dengan folder"</string>
- <string name="folder_cling_move_item" msgid="270598675060435169">"Untuk memindahkan aplikasi, sentuh &amp; tahankannya."</string>
<string name="folder_cling_create_folder" msgid="8352867485656129478">"Untuk membuat folder baharu pada skrin Utama anda, tindihkan satu aplikasi di atas yang lain."</string>
<string name="cling_dismiss" msgid="2780907108735868381">"OK"</string>
<string name="folder_opened" msgid="1262064100943801533">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> kali <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
@@ -107,8 +91,4 @@
<string name="folder_closed" msgid="3130534551370511932">"Folder ditutup"</string>
<string name="folder_renamed" msgid="7951233572858053642">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="3051680259794759037">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="custom_workspace_cling_title_1" msgid="1433009175359948587"></string>
- <string name="custom_workspace_cling_description_1" msgid="6875529190849858047"></string>
- <string name="custom_workspace_cling_title_2" msgid="5516006164661020362"></string>
- <string name="custom_workspace_cling_description_2" msgid="2758258454975288377"></string>
</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 28679be2e..9ecd07dd1 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -17,12 +17,16 @@
<resources>
<dimen name="app_icon_size">64dp</dimen>
+<!-- Apps view -->
+ <dimen name="apps_container_inset">24dp</dimen>
+ <dimen name="apps_grid_view_start_margin">64dp</dimen>
+ <dimen name="apps_view_section_text_size">26sp</dimen>
+ <dimen name="apps_view_row_height">76dp</dimen>
+
<!-- AppsCustomize -->
- <dimen name="apps_customize_tab_bar_height">60dp</dimen>
- <dimen name="apps_customize_tab_bar_margin_top">8dp</dimen>
- <dimen name="app_widget_preview_label_margin_top">8dp</dimen>
- <dimen name="app_widget_preview_label_margin_left">@dimen/app_widget_preview_padding_left</dimen>
- <dimen name="app_widget_preview_label_margin_right">@dimen/app_widget_preview_padding_right</dimen>
+ <dimen name="widget_preview_label_margin_top">8dp</dimen>
+ <dimen name="widget_preview_label_margin_left">@dimen/widget_preview_label_horizontal_padding</dimen>
+ <dimen name="widget_preview_label_margin_right">@dimen/widget_preview_label_horizontal_padding</dimen>
<!-- Cling -->
<dimen name="cling_migration_logo_height">400dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 8be996474..68fc1ecaf 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -21,11 +21,6 @@
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
<dimen name="toolbar_button_horizontal_padding">8dip</dimen>
- <!-- When dragging items on the workspace, the number of dps by which the position of
- the drag view should be offset from the position of the original view. -->
- <dimen name="dragViewOffsetX">0dp</dimen>
- <dimen name="dragViewOffsetY">0dp</dimen>
-
<!-- Cling -->
<dimen name="cling_migration_content_margin">96dp</dimen>
<dimen name="cling_migration_content_width">320dp</dimen>
diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml
index 11d2a1f82..3589e80ac 100644
--- a/res/values-v17/styles.xml
+++ b/res/values-v17/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="PagedViewWidgetImageView">
- <item name="android:paddingStart">@dimen/app_widget_preview_padding_left</item>
+ <style name="WidgetImageView">
+ <item name="android:paddingStart">@dimen/widget_preview_horizontal_padding</item>
</style>
</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3331cdec4..a1f28452a 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -18,6 +18,16 @@
<resources>
+ <!-- BubbleTextView specific attributes. -->
+ <declare-styleable name="BubbleTextView">
+ <attr name="layoutHorizontal" format="boolean" />
+ <attr name="iconSizeOverride" format="dimension" />
+ <attr name="iconPaddingOverride" format="dimension" />
+ <attr name="textSizeOverride" format="dimension" />
+ <attr name="deferShadowGeneration" format="boolean" />
+ <attr name="customShadows" format="boolean" />
+ </declare-styleable>
+
<!-- Page Indicator specific attributes. -->
<declare-styleable name="PageIndicator">
<attr name="windowSize" format="integer" />
@@ -80,11 +90,6 @@
<attr name="pageIndicator" format="reference" />
</declare-styleable>
- <declare-styleable name="BubbleTextView">
- <!-- A spacing override for the icons within a page -->
- <attr name="customShadows" format="boolean" />
- </declare-styleable>
-
<!-- AppsCustomizePagedView specific attributes. These attributes are used to
customize an AppsCustomizePagedView in xml files. -->
<declare-styleable name="AppsCustomizePagedView">
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 2daf9fe12..3a06bd95a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,4 +36,10 @@
<color name="outline_color">#FFFFFFFF</color>
<color name="widget_text_panel">#FF374248</color>
+ <!-- Apps view -->
+ <color name="apps_view_scrollbar_thumb_color">#009688</color>
+ <color name="apps_view_section_text_color">#009688</color>
+
+ <!-- Widgetss view -->
+ <color name="widgets_view_section_text_color">#009688</color>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index cbec51216..1acace6f9 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -104,4 +104,5 @@
<item type="id" name="action_uninstall" />
<item type="id" name="action_info" />
<item type="id" name="action_add_to_workspace" />
+ <item type="id" name="action_move" />
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 54689ec25..cad60fbae 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -46,19 +46,25 @@
<dimen name="toolbar_button_vertical_padding">4dip</dimen>
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
+<!-- Apps view -->
+ <dimen name="apps_container_width">0dp</dimen>
+ <dimen name="apps_container_height">0dp</dimen>
+ <dimen name="apps_container_inset">8dp</dimen>
+ <!-- Note: This needs to match the fixed insets for the search box -->
+ <dimen name="apps_container_fixed_bounds_inset">8dp</dimen>
+ <dimen name="apps_grid_view_start_margin">52dp</dimen>
+ <dimen name="apps_view_row_height">64dp</dimen>
+ <dimen name="apps_view_section_text_size">24sp</dimen>
+ <dimen name="apps_view_fast_scroll_bar_width">6dp</dimen>
+ <dimen name="apps_view_fast_scroll_bar_min_height">64dp</dimen>
+ <dimen name="apps_view_fast_scroll_scrubber_touch_inset">-16dp</dimen>
+ <dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
+ <dimen name="apps_view_fast_scroll_text_size">40dp</dimen>
+
<!-- AllApps/Customize/AppsCustomize -->
- <!-- The height of the tab bar - if this changes, we should update the
- external icon width/height above to compensate -->
- <dimen name="apps_customize_tab_bar_height">52dp</dimen>
- <dimen name="apps_customize_tab_bar_margin_top">0dp</dimen>
<dimen name="app_icon_size">48dp</dimen>
<dimen name="apps_customize_horizontal_padding">0dp</dimen>
- <!-- The AppsCustomize page indicator -->
- <dimen name="apps_customize_page_indicator_height">12dp</dimen>
- <dimen name="apps_customize_page_indicator_margin">4dp</dimen>
- <dimen name="apps_customize_page_indicator_offset">16dp</dimen>
-
<!-- Drag padding to add to the bottom of drop targets -->
<dimen name="drop_target_drag_padding">14dp</dimen>
<dimen name="drop_target_text_size">14sp</dimen>
@@ -68,20 +74,24 @@
or right while you're dragging. -->
<dimen name="scroll_zone">20dp</dimen>
- <!-- When dragging items on the workspace, the number of dps by which the position of
- the drag view should be offset from the position of the original view. -->
- <dimen name="dragViewOffsetX">0dp</dimen>
- <dimen name="dragViewOffsetY">0dp</dimen>
<!-- When dragging an item, how much bigger (fixed dps) the dragged view
should be. If 0, it will not be scaled at all. -->
<dimen name="dragViewScale">12dp</dimen>
- <!-- Padding applied to AppWidget previews -->
- <dimen name="app_widget_preview_padding_left">16dp</dimen>
- <dimen name="app_widget_preview_padding_right">16dp</dimen>
- <dimen name="app_widget_preview_padding_top">32dp</dimen>
- <dimen name="app_widget_preview_label_vertical_padding">8dp</dimen>
- <dimen name="app_widget_preview_label_horizontal_padding">8dp</dimen>
+<!-- Widget tray -->
+ <dimen name="widget_container_inset">8dp</dimen>
+ <dimen name="widget_preview_size">140dp</dimen>
+ <dimen name="widget_preview_padding_top">8dp</dimen>
+ <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
+ <dimen name="widget_preview_label_horizontal_padding">8dp</dimen>
+ <dimen name="widget_preview_horizontal_padding">8dp</dimen>
+
+ <dimen name="widget_section_height">52dp</dimen>
+ <dimen name="widget_section_icon_padding">8dp</dimen>
+
+ <!-- Equation: widget_preview_size + 2 * widget_preview_padding_horizontal -->
+ <dimen name="widget_preview_container_width">156dp</dimen>
+ <dimen name="widget_cell_height">160dp</dimen>
<!-- Padding applied to shortcut previews -->
<dimen name="shortcut_preview_padding_left">0dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8b7e6c199..52306e30e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,6 +38,8 @@
<string name="uid_name">Android Core Apps</string>
<!-- Default folder name -->
<string name="folder_name"></string>
+ <!-- Work folder name -->
+ <string name="work_folder_name">Work</string>
<!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
<string name="activity_not_found">App isn\'t installed.</string>
<!-- Displayed when user selects a shortcut for an app that is current not available [CHAR_LIMIT=none]-->
@@ -71,6 +73,14 @@
drop if there are multiple choices. [CHAR_LIMIT=35] -->
<string name="external_drop_widget_pick_title">Choose widget to create</string>
+ <!-- Apps view -->
+ <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
+ <string name="apps_view_search_bar_hint">Search Apps</string>
+ <!-- Loading apps text. [CHAR_LIMIT=50] -->
+ <string name="loading_apps_message">Loading Apps...</string>
+ <!-- No-search-results text. [CHAR_LIMIT=50] -->
+ <string name="apps_view_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
+
<!-- Folders -->
<skip />
<!-- Label of Folder name field in Rename folder dialog box -->
@@ -81,6 +91,8 @@
<string name="rename_action">OK</string>
<!-- Buttons in Rename folder dialog box -->
<string name="cancel_action">Cancel</string>
+ <!-- Label for button to sort folder contents. [CHAR_LIMIT=10] -->
+ <string name="sort_alphabetical">A-Z</string>
<!-- Shortcuts -->
<skip />
@@ -103,8 +115,6 @@ s -->
<string name="invalid_hotseat_item">This widget is too large for the Favorites tray</string>
<!-- Message displayed when a shortcut is created by an external application -->
<string name="shortcut_installed">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" created.</string>
- <!-- Message displayed when a shortcut is uninstalled by an external application -->
- <string name="shortcut_uninstalled">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" was removed.</string>
<!-- Message displayed when an external application attemps to create a shortcut that already exists -->
<string name="shortcut_duplicate">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" already exists.</string>
@@ -166,11 +176,6 @@ s -->
<string name="permdesc_install_shortcut">Allows an app to add
shortcuts without user intervention.</string>
<!-- Permission short label -->
- <string name="permlab_uninstall_shortcut">uninstall shortcuts</string>
- <!-- Permission description -->
- <string name="permdesc_uninstall_shortcut">Allows the app to remove
- shortcuts without user intervention.</string>
- <!-- Permission short label -->
<string name="permlab_read_settings">read Home settings and shortcuts</string>
<!-- Permission description -->
<string name="permdesc_read_settings">Allows the app to read the settings and
@@ -303,6 +308,34 @@ s -->
</string>
<!-- Strings for accessibility actions -->
- <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
+ <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
<string name="action_add_to_workspace">Add To Workspace</string>
+
+ <!-- Accessibility confirmation for item added to workspace [DO NOT TRANSLATE] -->
+ <string name="item_added_to_workspace">Item added to workspace</string>
+
+ <!-- Accessibility confirmation for item removed [DO NOT TRANSLATE] -->
+ <string name="item_removed_from_workspace">Item removed from workspace</string>
+
+ <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
+ <string name="action_move">Move Item</string>
+
+ <!-- Accessibility description to move item to empty cell. [DO NOT TRANSLATE] -->
+ <string name="move_to_empty_cell">Move to empty cell <xliff:g id="number" example="1">%1$s</xliff:g>, <xliff:g id="number" example="1">%2$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for item move [DO NOT TRANSLATE]-->
+ <string name="item_moved">Item moved</string>
+
+ <!-- Accessibility description to move item into an existing folder. [DO NOT TRANSLATE]-->
+ <string name="add_to_folder">Add to folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for item added to folder [DO NOT TRANSLATE] -->
+ <string name="added_to_folder">Item added to folder</string>
+
+ <!-- Accessibility description to create folder with another item. [DO NOT TRANSLATE] -->
+ <string name="create_folder_with">Create folder with: <xliff:g id="name" example="Game">%1$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for folder created [DO NOT TRANSLATE] -->
+ <string name="folder_created">Folder created</string>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 77798f174..16d4cbeed 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -91,8 +91,8 @@
</style>
<!-- Overridden in device overlays -->
- <style name="PagedViewWidgetImageView">
- <item name="android:paddingLeft">@dimen/app_widget_preview_padding_left</item>
+ <style name="WidgetImageView">
+ <item name="android:paddingLeft">@dimen/widget_preview_horizontal_padding</item>
</style>
</resources>
diff --git a/res/xml/app_target_browser.xml b/res/xml/app_target_browser.xml
new file mode 100644
index 000000000..d7c3ed5fd
--- /dev/null
+++ b/res/xml/app_target_browser.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+ <favorite launcher:uri="http://www.example.com/" />
+
+</resolve> \ No newline at end of file
diff --git a/res/xml/app_target_camera.xml b/res/xml/app_target_camera.xml
new file mode 100644
index 000000000..f65a2b168
--- /dev/null
+++ b/res/xml/app_target_camera.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+
+</resolve> \ No newline at end of file
diff --git a/res/xml/app_target_email.xml b/res/xml/app_target_email.xml
new file mode 100644
index 000000000..44f0a407d
--- /dev/null
+++ b/res/xml/app_target_email.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+ <favorite launcher:uri="mailto:" />
+
+</resolve> \ No newline at end of file
diff --git a/res/xml/app_target_gallery.xml b/res/xml/app_target_gallery.xml
new file mode 100644
index 000000000..c9d34924a
--- /dev/null
+++ b/res/xml/app_target_gallery.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+ <favorite launcher:uri="#Intent;type=images/*;end" />
+
+</resolve> \ No newline at end of file
diff --git a/res/xml/app_target_messenger.xml b/res/xml/app_target_messenger.xml
new file mode 100644
index 000000000..278eb5ca8
--- /dev/null
+++ b/res/xml/app_target_messenger.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+ <favorite launcher:uri="sms:" />
+ <favorite launcher:uri="smsto:" />
+ <favorite launcher:uri="mms:" />
+ <favorite launcher:uri="mmsto:" />
+
+</resolve> \ No newline at end of file
diff --git a/res/xml/app_target_phone.xml b/res/xml/app_target_phone.xml
new file mode 100644
index 000000000..5d6ca316e
--- /dev/null
+++ b/res/xml/app_target_phone.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
+ <favorite launcher:uri="tel:123" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+
+</resolve> \ No newline at end of file
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 72c6693b3..dd646bb22 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -98,14 +98,14 @@ class AllAppsList {
user);
for (LauncherActivityInfoCompat info : matches) {
- add(new AppInfo(context, info, user, mIconCache, null));
+ add(new AppInfo(context, info, user, mIconCache));
}
}
/**
* Remove the apps for the given apk identified by packageName.
*/
- public void removePackage(String packageName, UserHandleCompat user, boolean clearCache) {
+ public void removePackage(String packageName, UserHandleCompat user) {
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
@@ -115,9 +115,6 @@ class AllAppsList {
data.remove(i);
}
}
- if (clearCache) {
- mIconCache.remove(packageName, user);
- }
}
/**
@@ -137,7 +134,6 @@ class AllAppsList {
&& packageName.equals(component.getPackageName())) {
if (!findActivity(matches, component)) {
removed.add(applicationInfo);
- mIconCache.remove(component, user);
data.remove(i);
}
}
@@ -150,10 +146,9 @@ class AllAppsList {
info.getComponentName().getPackageName(), user,
info.getComponentName().getClassName());
if (applicationInfo == null) {
- add(new AppInfo(context, info, user, mIconCache, null));
+ add(new AppInfo(context, info, user, mIconCache));
} else {
- mIconCache.remove(applicationInfo.componentName, user);
- mIconCache.getTitleAndIcon(applicationInfo, info, null);
+ mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
modified.add(applicationInfo);
}
}
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
new file mode 100644
index 000000000..2ee5a62ed
--- /dev/null
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -0,0 +1,316 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * A private class to manage access to an app name comparator.
+ */
+class AppNameComparator {
+ private UserManagerCompat mUserManager;
+ private Comparator<AppInfo> mAppNameComparator;
+ private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
+
+ public AppNameComparator(Context context) {
+ final Collator collator = Collator.getInstance();
+ mUserManager = UserManagerCompat.getInstance(context);
+ mAppNameComparator = new Comparator<AppInfo>() {
+ public final int compare(AppInfo a, AppInfo b) {
+ // Order by the title
+ int result = collator.compare(a.title.toString().trim(),
+ b.title.toString().trim());
+ if (result == 0) {
+ // If two apps have the same title, then order by the component name
+ result = a.componentName.compareTo(b.componentName);
+ if (result == 0) {
+ // If the two apps are the same component, then prioritize by the order that
+ // the app user was created (prioritizing the main user's apps)
+ if (UserHandleCompat.myUserHandle().equals(a.user)) {
+ return -1;
+ } else {
+ Long aUserSerial = getAndCacheUserSerial(a.user);
+ Long bUserSerial = getAndCacheUserSerial(b.user);
+ return aUserSerial.compareTo(bUserSerial);
+ }
+ }
+ }
+ return result;
+ }
+ };
+ }
+
+ /**
+ * Returns a locale-aware comparator that will alphabetically order a list of applications.
+ */
+ public Comparator<AppInfo> getComparator() {
+ // Clear the user serial cache so that we get serials as needed in the comparator
+ mUserSerialCache.clear();
+ return mAppNameComparator;
+ }
+
+ /**
+ * Returns the user serial for this user, using a cached serial if possible.
+ */
+ private Long getAndCacheUserSerial(UserHandleCompat user) {
+ Long userSerial = mUserSerialCache.get(user);
+ if (userSerial == null) {
+ userSerial = mUserManager.getSerialNumberForUser(user);
+ mUserSerialCache.put(user, userSerial);
+ }
+ return userSerial;
+ }
+}
+
+/**
+ * The alphabetically sorted list of applications.
+ */
+public class AlphabeticalAppsList {
+
+ /**
+ * Info about a section in the alphabetic list
+ */
+ public static class SectionInfo {
+ // The name of this section
+ public String sectionName;
+ // The number of applications in this section
+ public int numAppsInSection;
+ // The first app AdapterItem for this section
+ public AdapterItem firstAppItem;
+
+ public SectionInfo(String name) {
+ sectionName = name;
+ }
+ }
+
+ /**
+ * Info about a particular adapter item (can be either section or app)
+ */
+ public static class AdapterItem {
+ // The index of this adapter item in the list
+ public int position;
+ // Whether or not the item at this adapter position is a section or not
+ public boolean isSectionHeader;
+ // The name of this section, or the section that this app is contained in
+ public String sectionName;
+ // The associated AppInfo, or null if this adapter item is a section
+ public AppInfo appInfo;
+ // The index of this app (not including sections), or -1 if this adapter item is a section
+ public int appIndex;
+
+ public static AdapterItem asSection(int pos, String name) {
+ AdapterItem item = new AdapterItem();
+ item.position = pos;
+ item.isSectionHeader = true;
+ item.sectionName = name;
+ item.appInfo = null;
+ item.appIndex = -1;
+ return item;
+ }
+
+ public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo, int appIndex) {
+ AdapterItem item = new AdapterItem();
+ item.position = pos;
+ item.isSectionHeader = false;
+ item.sectionName = sectionName;
+ item.appInfo = appInfo;
+ item.appIndex = appIndex;
+ return item;
+ }
+ }
+
+ /**
+ * A filter interface to limit the set of applications in the apps list.
+ */
+ public interface Filter {
+ public boolean retainApp(AppInfo info, String sectionName);
+ }
+
+ private List<AppInfo> mApps = new ArrayList<>();
+ private List<AppInfo> mFilteredApps = new ArrayList<>();
+ private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
+ private List<SectionInfo> mSections = new ArrayList<>();
+ private RecyclerView.Adapter mAdapter;
+ private Filter mFilter;
+ private AlphabeticIndexCompat mIndexer;
+ private AppNameComparator mAppNameComparator;
+
+ public AlphabeticalAppsList(Context context) {
+ mIndexer = new AlphabeticIndexCompat(context);
+ mAppNameComparator = new AppNameComparator(context);
+ }
+
+ /**
+ * Sets the adapter to notify when this dataset changes.
+ */
+ public void setAdapter(RecyclerView.Adapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Returns sections of all the current filtered applications.
+ */
+ public List<SectionInfo> getSections() {
+ return mSections;
+ }
+
+ /**
+ * Returns the current filtered list of applications broken down into their sections.
+ */
+ public List<AdapterItem> getAdapterItems() {
+ return mSectionedFilteredApps;
+ }
+
+ /**
+ * Returns the number of applications in this list.
+ */
+ public int getSize() {
+ return mFilteredApps.size();
+ }
+
+ /**
+ * Returns whether there are no filtered results.
+ */
+ public boolean hasNoFilteredResults() {
+ return (mFilter != null) && mFilteredApps.isEmpty();
+ }
+
+ /**
+ * Sets the current filter for this list of apps.
+ */
+ public void setFilter(Filter f) {
+ mFilter = f;
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Sets the current set of apps.
+ */
+ public void setApps(List<AppInfo> apps) {
+ Collections.sort(apps, mAppNameComparator.getComparator());
+ mApps.clear();
+ mApps.addAll(apps);
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Adds new apps to the list.
+ */
+ public void addApps(List<AppInfo> apps) {
+ // We add it in place, in alphabetical order
+ for (AppInfo info : apps) {
+ addApp(info);
+ }
+ }
+
+ /**
+ * Updates existing apps in the list
+ */
+ public void updateApps(List<AppInfo> apps) {
+ for (AppInfo info : apps) {
+ int index = mApps.indexOf(info);
+ if (index != -1) {
+ mApps.set(index, info);
+ onAppsUpdated();
+ mAdapter.notifyItemChanged(index);
+ } else {
+ addApp(info);
+ }
+ }
+ }
+
+ /**
+ * Removes some apps from the list.
+ */
+ public void removeApps(List<AppInfo> apps) {
+ for (AppInfo info : apps) {
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex != -1) {
+ mApps.remove(removeIndex);
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+
+ /**
+ * Finds the index of an app given a target AppInfo.
+ */
+ private int findAppByComponent(List<AppInfo> apps, AppInfo targetInfo) {
+ ComponentName targetComponent = targetInfo.intent.getComponent();
+ int length = apps.size();
+ for (int i = 0; i < length; ++i) {
+ AppInfo info = apps.get(i);
+ if (info.user.equals(info.user)
+ && info.intent.getComponent().equals(targetComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Implementation to actually add an app to the alphabetic list
+ */
+ private void addApp(AppInfo info) {
+ int index = Collections.binarySearch(mApps, info, mAppNameComparator.getComparator());
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ onAppsUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Updates internals when the set of apps are updated.
+ */
+ private void onAppsUpdated() {
+ // Recreate the filtered and sectioned apps (for convenience for the grid layout)
+ mFilteredApps.clear();
+ mSections.clear();
+ mSectionedFilteredApps.clear();
+ SectionInfo lastSectionInfo = null;
+ int position = 0;
+ int appIndex = 0;
+ for (AppInfo info : mApps) {
+ String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
+
+ // Check if we want to retain this app
+ if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
+ continue;
+ }
+
+ // Create a new section if necessary
+ if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
+ lastSectionInfo = new SectionInfo(sectionName);
+ mSections.add(lastSectionInfo);
+
+ // Create a new section item
+ AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
+ mSectionedFilteredApps.add(sectionItem);
+ }
+
+ // Create an app item
+ AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
+ lastSectionInfo.numAppsInSection++;
+ if (lastSectionInfo.firstAppItem == null) {
+ lastSectionInfo.firstAppItem = appItem;
+ }
+ mSectionedFilteredApps.add(appItem);
+ mFilteredApps.add(info);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index a66bac08a..7c6b0664c 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -19,19 +19,15 @@ package com.android.launcher3;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.util.Log;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
/**
* Represents an app in AllAppsView.
@@ -47,14 +43,19 @@ public class AppInfo extends ItemInfo {
/**
* A bitmap version of the application icon.
*/
- Bitmap iconBitmap;
+ public Bitmap iconBitmap;
+
+ /**
+ * Indicates whether we're using a low res icon
+ */
+ boolean usingLowResIcon;
/**
* The time at which the app was first installed.
*/
long firstInstallTime;
- ComponentName componentName;
+ public ComponentName componentName;
static final int DOWNLOADED_FLAG = 1;
static final int UPDATED_SYSTEM_APP_FLAG = 2;
@@ -77,13 +78,13 @@ public class AppInfo extends ItemInfo {
* Must not hold the Context.
*/
public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
- IconCache iconCache, HashMap<Object, CharSequence> labelCache) {
+ IconCache iconCache) {
this.componentName = info.getComponentName();
this.container = ItemInfo.NO_ID;
flags = initFlags(info);
firstInstallTime = info.getFirstInstallTime();
- iconCache.getTitleAndIcon(this, info, labelCache);
+ iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */);
intent = makeLaunchIntent(context, info, user);
this.user = user;
}
@@ -120,12 +121,15 @@ public class AppInfo extends ItemInfo {
+ " user=" + user + ")";
}
+ /**
+ * Helper method used for debugging.
+ */
public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
Log.d(tag, label + " size=" + list.size());
for (AppInfo info: list) {
- Log.d(tag, " title=\"" + info.title + "\" iconBitmap="
- + info.iconBitmap + " firstInstallTime="
- + info.firstInstallTime);
+ Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
+ + " firstInstallTime=" + info.firstInstallTime
+ + " componentName=" + info.componentName.getPackageName());
}
}
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 880aaf1ec..5e7a012d2 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -90,5 +90,10 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
+
+ LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+ if (app != null) {
+ app.reloadWorkspace();
+ }
}
}
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
new file mode 100644
index 000000000..16244ee35
--- /dev/null
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2015 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.launcher3;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import java.util.List;
+
+/**
+ * A RecyclerView with custom fastscroll support. This is the main container for the all apps
+ * icons.
+ */
+public class AppsContainerRecyclerView extends RecyclerView
+ implements RecyclerView.OnItemTouchListener {
+
+ private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
+
+ private AlphabeticalAppsList mApps;
+ private int mNumAppsPerRow;
+
+ private Drawable mScrollbar;
+ private Drawable mFastScrollerBg;
+ private Rect mVerticalScrollbarBounds = new Rect();
+ private boolean mDraggingFastScroller;
+ private String mFastScrollSectionName;
+ private Paint mFastScrollTextPaint;
+ private Rect mFastScrollTextBounds = new Rect();
+ private float mFastScrollAlpha;
+ private int mDownX;
+ private int mDownY;
+ private int mLastX;
+ private int mLastY;
+ private int mScrollbarWidth;
+ private int mScrollbarMinHeight;
+ private int mScrollbarInset;
+
+ public AppsContainerRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public AppsContainerRecyclerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr);
+
+ Resources res = context.getResources();
+ int fastScrollerSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_popup_size);
+ mScrollbar = res.getDrawable(R.drawable.apps_list_scrollbar_thumb);
+ mFastScrollerBg = res.getDrawable(R.drawable.apps_list_fastscroll_bg);
+ mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
+ mFastScrollTextPaint = new Paint();
+ mFastScrollTextPaint.setColor(Color.WHITE);
+ mFastScrollTextPaint.setAntiAlias(true);
+ mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.apps_view_fast_scroll_text_size));
+ mScrollbarWidth = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_bar_width);
+ mScrollbarMinHeight =
+ res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_bar_min_height);
+ mScrollbarInset =
+ res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
+ setFastScrollerAlpha(getFastScrollerAlpha());
+ }
+
+ /**
+ * Sets the list of apps in this view, used to determine the fastscroll position.
+ */
+ public void setApps(AlphabeticalAppsList apps) {
+ mApps = apps;
+ }
+
+ /**
+ * Sets the number of apps per row in this recycler view.
+ */
+ public void setNumAppsPerRow(int rowSize) {
+ mNumAppsPerRow = rowSize;
+ }
+
+ /**
+ * Sets the fast scroller alpha.
+ */
+ public void setFastScrollerAlpha(float alpha) {
+ mFastScrollAlpha = alpha;
+ invalidateFastScroller();
+ }
+
+ /**
+ * Gets the fast scroller alpha.
+ */
+ public float getFastScrollerAlpha() {
+ return mFastScrollAlpha;
+ }
+
+ /**
+ * Returns the scroll bar width.
+ */
+ public int getScrollbarWidth() {
+ return mScrollbarWidth;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ addOnItemTouchListener(this);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ drawVerticalScrubber(canvas);
+ drawFastScrollerPopup(canvas);
+ }
+
+ /**
+ * We intercept the touch handling only to support fast scrolling when initiated from the
+ * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+ handleTouchEvent(ev);
+ }
+
+ /**
+ * Handles the touch event and determines whether to show the fast scroller (or updates it if
+ * it is already showing).
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+
+ int action = ev.getAction();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ // Keep track of the down positions
+ mDownX = mLastX = x;
+ mDownY = mLastY = y;
+ stopScroll();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Check if we are scrolling
+ if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) &&
+ Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ mDraggingFastScroller = true;
+ animateFastScrollerVisibility(true);
+ }
+ if (mDraggingFastScroller) {
+ mLastX = x;
+ mLastY = y;
+
+ // Scroll to the right position, and update the section name
+ int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
+ int bottom = getHeight() - getPaddingBottom() -
+ (mFastScrollerBg.getBounds().height() / 2);
+ float boundedY = (float) Math.max(top, Math.min(bottom, y));
+ mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
+ (bottom - top));
+ invalidateFastScroller();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDraggingFastScroller = false;
+ animateFastScrollerVisibility(false);
+ break;
+ }
+ return mDraggingFastScroller;
+ }
+
+ /**
+ * Animates the visibility of the fast scroller popup.
+ */
+ private void animateFastScrollerVisibility(boolean visible) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
+ anim.setDuration(visible ? 200 : 150);
+ anim.start();
+ }
+
+ /**
+ * Returns whether a given point is near the scrollbar.
+ */
+ private boolean isPointNearScrollbar(int x, int y) {
+ // Check if we are scrolling
+ updateVerticalScrollbarBounds();
+ mVerticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset);
+ return mVerticalScrollbarBounds.contains(x, y);
+ }
+
+ /**
+ * Draws the fast scroller popup.
+ */
+ private void drawFastScrollerPopup(Canvas canvas) {
+ if (mFastScrollAlpha > 0f) {
+ int x;
+ int y;
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+
+ // Calculate the position for the fast scroller popup
+ Rect bgBounds = mFastScrollerBg.getBounds();
+ if (isRtl) {
+ x = getPaddingLeft() + getScrollBarSize();
+ } else {
+ x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
+ }
+ y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
+ y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
+ bgBounds.height()));
+
+ // Draw the fast scroller popup
+ int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(x, y);
+ mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollerBg.draw(canvas);
+ mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
+ mFastScrollSectionName.length(), mFastScrollTextBounds);
+ canvas.drawText(mFastScrollSectionName,
+ (bgBounds.width() - mFastScrollTextBounds.width()) / 2,
+ bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
+ mFastScrollTextPaint);
+ canvas.restoreToCount(restoreCount);
+ }
+ }
+
+ /**
+ * Draws the vertical scrollbar.
+ */
+ private void drawVerticalScrubber(Canvas canvas) {
+ updateVerticalScrollbarBounds();
+
+ // Draw the scroll bar
+ int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(mVerticalScrollbarBounds.left, mVerticalScrollbarBounds.top);
+ mScrollbar.setBounds(0, 0, mScrollbarWidth, mVerticalScrollbarBounds.height());
+ mScrollbar.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+
+ /**
+ * Invalidates the fast scroller popup.
+ */
+ private void invalidateFastScroller() {
+ invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
+ mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
+ }
+
+ /**
+ * Maps the progress (from 0..1) to the position that should be visible
+ */
+ private String scrollToPositionAtProgress(float progress) {
+ List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
+ if (sections.isEmpty()) {
+ return "";
+ }
+
+ // Find the position of the first application in the section that contains the row at the
+ // current progress
+ int rowAtProgress = (int) (progress * getNumRows());
+ int rowCount = 0;
+ AlphabeticalAppsList.SectionInfo lastSectionInfo = null;
+ for (AlphabeticalAppsList.SectionInfo section : sections) {
+ int numRowsInSection = (int) Math.ceil((float) section.numAppsInSection / mNumAppsPerRow);
+ if (rowCount + numRowsInSection >= rowAtProgress) {
+ lastSectionInfo = section;
+ break;
+ }
+ rowCount += numRowsInSection;
+ }
+ int position = mApps.getAdapterItems().indexOf(lastSectionInfo.firstAppItem);
+
+ // Scroll the position into view, anchored at the top of the screen if possible. We call the
+ // scroll method on the LayoutManager directly since it is not exposed by RecyclerView.
+ LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+ stopScroll();
+ layoutManager.scrollToPositionWithOffset(position, 0);
+
+ // Return the section name of the row
+ return mApps.getAdapterItems().get(position).sectionName;
+ }
+
+ /**
+ * Returns the bounds for the scrollbar.
+ */
+ private void updateVerticalScrollbarBounds() {
+ // Skip early if there are no items
+ if (mApps.getAdapterItems().isEmpty()) {
+ mVerticalScrollbarBounds.setEmpty();
+ return;
+ }
+
+ // Find the index and height of the first visible row (all rows have the same height)
+ int x;
+ int y;
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+ int rowIndex = -1;
+ int rowTopOffset = -1;
+ int rowHeight = -1;
+ int rowCount = getNumRows();
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ int position = getChildPosition(child);
+ if (position != NO_POSITION) {
+ AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+ if (!item.isSectionHeader) {
+ rowIndex = findRowForAppIndex(item.appIndex);
+ rowTopOffset = getLayoutManager().getDecoratedTop(child);
+ rowHeight = child.getHeight();
+ break;
+ }
+ }
+ }
+
+ if (rowIndex != -1) {
+ int height = getHeight() - getPaddingTop() - getPaddingBottom();
+ int totalScrollHeight = rowCount * rowHeight;
+ if (totalScrollHeight > height) {
+ int scrollbarHeight = Math.max(mScrollbarMinHeight,
+ (int) (height / ((float) totalScrollHeight / height)));
+
+ // Calculate the position and size of the scroll bar
+ if (isRtl) {
+ x = getPaddingLeft();
+ } else {
+ x = getWidth() - getPaddingRight() - mScrollbarWidth;
+ }
+
+ // To calculate the offset, we compute the percentage of the total scrollable height
+ // that the user has already scrolled and then map that to the scroll bar bounds
+ int availableY = totalScrollHeight - height;
+ int availableScrollY = height - scrollbarHeight;
+ y = (rowIndex * rowHeight) - rowTopOffset;
+ y = getPaddingTop() +
+ (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
+
+ mVerticalScrollbarBounds.set(x, y, x + mScrollbarWidth, y + scrollbarHeight);
+ return;
+ }
+ }
+ mVerticalScrollbarBounds.setEmpty();
+ }
+
+ /**
+ * Returns the row index for a given position in the list.
+ */
+ private int findRowForAppIndex(int position) {
+ List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
+ int appIndex = 0;
+ int rowCount = 0;
+ for (AlphabeticalAppsList.SectionInfo info : sections) {
+ int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
+ if (appIndex + info.numAppsInSection > position) {
+ return rowCount + ((position - appIndex) / mNumAppsPerRow);
+ }
+ appIndex += info.numAppsInSection;
+ rowCount += numRowsInSection;
+ }
+ return appIndex;
+ }
+
+ /**
+ * Returns the total number of rows in the list.
+ */
+ private int getNumRows() {
+ List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
+ int rowCount = 0;
+ for (AlphabeticalAppsList.SectionInfo info : sections) {
+ int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
+ rowCount += numRowsInSection;
+ }
+ return rowCount;
+ }
+}
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
new file mode 100644
index 000000000..b24714505
--- /dev/null
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -0,0 +1,549 @@
+/*
+ * Copyright (C) 2015 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.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.InsetDrawable;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.util.Thunk;
+
+import java.util.List;
+
+
+/**
+ * The all apps list view container.
+ */
+public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher,
+ TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
+ View.OnLongClickListener {
+
+ private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
+
+ private static final int GRID_LAYOUT = 0;
+ private static final int LIST_LAYOUT = 1;
+ private static final int USE_LAYOUT = GRID_LAYOUT;
+
+ @Thunk Launcher mLauncher;
+ @Thunk AlphabeticalAppsList mApps;
+ private RecyclerView.Adapter mAdapter;
+ private RecyclerView.LayoutManager mLayoutManager;
+ private RecyclerView.ItemDecoration mItemDecoration;
+
+ private LinearLayout mContentView;
+ @Thunk AppsContainerRecyclerView mAppsRecyclerView;
+ private EditText mSearchBarView;
+
+ private int mNumAppsPerRow;
+ private Point mLastTouchDownPos = new Point(-1, -1);
+ private Point mLastTouchPos = new Point();
+ private Rect mInsets = new Rect();
+ private Rect mFixedBounds = new Rect();
+ private int mContentMarginStart;
+ // Normal container insets
+ private int mContainerInset;
+ // Fixed bounds container insets
+ private int mFixedBoundsContainerInset;
+
+ public AppsContainerView(Context context) {
+ this(context, null);
+ }
+
+ public AppsContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Resources res = context.getResources();
+
+ mContainerInset = context.getResources().getDimensionPixelSize(
+ R.dimen.apps_container_inset);
+ mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
+ R.dimen.apps_container_fixed_bounds_inset);
+ mLauncher = (Launcher) context;
+ mApps = new AlphabeticalAppsList(context);
+ if (USE_LAYOUT == GRID_LAYOUT) {
+ mNumAppsPerRow = grid.appsViewNumCols;
+ AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this,
+ mLauncher, this);
+ adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
+ mLayoutManager = adapter.getLayoutManager(context);
+ mItemDecoration = adapter.getItemDecoration();
+ mAdapter = adapter;
+ mContentMarginStart = adapter.getContentMarginStart();
+ } else if (USE_LAYOUT == LIST_LAYOUT) {
+ mNumAppsPerRow = 1;
+ AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this);
+ adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
+ mLayoutManager = adapter.getLayoutManager(context);
+ mAdapter = adapter;
+ }
+ mApps.setAdapter(mAdapter);
+ }
+
+ /**
+ * Sets the current set of apps.
+ */
+ public void setApps(List<AppInfo> apps) {
+ mApps.setApps(apps);
+ }
+
+ /**
+ * Adds new apps to the list.
+ */
+ public void addApps(List<AppInfo> apps) {
+ mApps.addApps(apps);
+ }
+
+ /**
+ * Updates existing apps in the list
+ */
+ public void updateApps(List<AppInfo> apps) {
+ mApps.updateApps(apps);
+ }
+
+ /**
+ * Removes some apps from the list.
+ */
+ public void removeApps(List<AppInfo> apps) {
+ mApps.removeApps(apps);
+ }
+
+ /**
+ * Hides the search bar
+ */
+ public void hideSearchBar() {
+ mSearchBarView.setVisibility(View.GONE);
+ updateBackgrounds();
+ updatePaddings();
+ }
+
+ /**
+ * Scrolls this list view to the top.
+ */
+ public void scrollToTop() {
+ mAppsRecyclerView.scrollToPosition(0);
+ }
+
+ /**
+ * Returns the content view used for the launcher transitions.
+ */
+ public View getContentView() {
+ return mContentView;
+ }
+
+ /**
+ * Returns the reveal view used for the launcher transitions.
+ */
+ public View getRevealView() {
+ return findViewById(R.id.apps_view_transition_overlay);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+ if (USE_LAYOUT == GRID_LAYOUT) {
+ ((AppsGridAdapter) mAdapter).setRtl(isRtl);
+ }
+
+ // Work around the search box getting first focus and showing the cursor by
+ // proxying the focus from the content view to the recycler view directly
+ mContentView = (LinearLayout) findViewById(R.id.apps_list);
+ mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (v == mContentView && hasFocus) {
+ mAppsRecyclerView.requestFocus();
+ }
+ }
+ });
+ mSearchBarView = (EditText) findViewById(R.id.app_search_box);
+ if (mSearchBarView != null) {
+ mSearchBarView.addTextChangedListener(this);
+ mSearchBarView.setOnEditorActionListener(this);
+ }
+ mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
+ mAppsRecyclerView.setApps(mApps);
+ mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
+ mAppsRecyclerView.setLayoutManager(mLayoutManager);
+ mAppsRecyclerView.setAdapter(mAdapter);
+ mAppsRecyclerView.setHasFixedSize(true);
+ if (mItemDecoration != null) {
+ mAppsRecyclerView.addItemDecoration(mItemDecoration);
+ }
+ updateBackgrounds();
+ updatePaddings();
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ updatePaddings();
+ }
+
+ /**
+ * Sets the fixed bounds for this Apps view.
+ */
+ public void setFixedBounds(Context context, Rect fixedBounds) {
+ if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
+ // Update the number of items in the grid
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ if (grid.updateAppsViewNumCols(context.getResources(), fixedBounds.width())) {
+ mNumAppsPerRow = grid.appsViewNumCols;
+ mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
+ if (USE_LAYOUT == GRID_LAYOUT) {
+ ((AppsGridAdapter) mAdapter).setNumAppsPerRow(mNumAppsPerRow);
+ }
+ }
+
+ mFixedBounds.set(fixedBounds);
+ }
+ updateBackgrounds();
+ updatePaddings();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // Return early if this is not initiated from a touch
+ if (!v.isInTouchMode()) return false;
+ // When we have exited all apps or are in transition, disregard long clicks
+ if (!mLauncher.isAppsViewVisible() ||
+ mLauncher.getWorkspace().isSwitchingState()) return false;
+ // Return if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+
+ // Start the drag
+ mLauncher.getWorkspace().beginDragShared(v, mLastTouchPos, this, 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()) {
+ // Go into spring loaded mode (must happen before we startDrag())
+ mLauncher.enterSpringLoadedDragMode();
+ }
+ }
+ }, 150);
+
+ return false;
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsAppInfoDropTarget() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return false;
+ }
+
+ @Override
+ public float getIntrinsicIconScaleFactor() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ return (float) grid.allAppsIconSizePx / grid.iconSizePx;
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // We just dismiss the drag when we fling, so cleanup here
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ mLauncher.unlockScreenOrientation(false);
+ }
+
+ @Override
+ public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+ boolean success) {
+ if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+ !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+ // Exit spring loaded mode if we have not successfully dropped or have not handled the
+ // drop in Workspace
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ }
+ mLauncher.unlockScreenOrientation(false);
+
+ // 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;
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ if (s.toString().isEmpty()) {
+ mApps.setFilter(null);
+ } else {
+ String formatStr = getResources().getString(R.string.apps_view_no_search_results);
+ if (USE_LAYOUT == GRID_LAYOUT) {
+ ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
+ s.toString()));
+ } else {
+ ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
+ s.toString()));
+ }
+
+ final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
+ mApps.setFilter(new AlphabeticalAppsList.Filter() {
+ @Override
+ public boolean retainApp(AppInfo info, String sectionName) {
+ String title = info.title.toString();
+ return sectionName.toLowerCase().contains(filterText) ||
+ title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
+ // Skip the quick-launch if there isn't exactly one item
+ if (mApps.getSize() != 1) {
+ return false;
+ }
+
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+ for (int i = 0; i < items.size(); i++) {
+ AlphabeticalAppsList.AdapterItem item = items.get(i);
+ if (!item.isSectionHeader) {
+ mAppsRecyclerView.getChildAt(i).performClick();
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public View getContent() {
+ return null;
+ }
+
+ @Override
+ public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ if (mSearchBarView != null) {
+ if (toWorkspace) {
+ // Clear the search bar
+ mSearchBarView.setText("");
+ }
+ }
+ }
+
+ /**
+ * Handles the touch events to dismiss all apps when clicking outside the bounds of the
+ * recycler view.
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (!mFixedBounds.isEmpty()) {
+ // Outset the fixed bounds and check if the touch is outside all apps
+ Rect tmpRect = new Rect(mFixedBounds);
+ tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
+ if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
+ mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
+ return true;
+ }
+ } else {
+ // Check if the touch is outside all apps
+ if (ev.getX() < getPaddingLeft() ||
+ ev.getX() > (getWidth() - getPaddingRight())) {
+ mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mLastTouchDownPos.x > -1) {
+ ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
+ float dx = ev.getX() - mLastTouchDownPos.x;
+ float dy = ev.getY() - mLastTouchDownPos.y;
+ float distance = (float) Math.hypot(dx, dy);
+ if (distance < viewConfig.getScaledTouchSlop()) {
+ // The background was clicked, so just go home
+ Launcher launcher = (Launcher) getContext();
+ launcher.showWorkspace(true);
+ return true;
+ }
+ }
+ // Fall through
+ case MotionEvent.ACTION_CANCEL:
+ mLastTouchDownPos.set(-1, -1);
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Update the padding of the Apps view and children. To ensure that the RecyclerView has the
+ * full width to handle touches right to the edge of the screen, we only apply the top and
+ * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
+ * itself. In particular, the left/right padding is applied to the background of the view,
+ * and then additionally inset by the start margin.
+ */
+ private void updatePaddings() {
+ boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+ LAYOUT_DIRECTION_RTL);
+ boolean hasSearchBar = (mSearchBarView != null) &&
+ (mSearchBarView.getVisibility() == View.VISIBLE);
+
+ if (mFixedBounds.isEmpty()) {
+ // If there are no fixed bounds, then use the default padding and insets
+ setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
+ mContainerInset + mInsets.bottom);
+ } else {
+ // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
+ setPadding(mFixedBounds.left, mFixedBounds.top + mFixedBoundsContainerInset,
+ getMeasuredWidth() - mFixedBounds.right,
+ mInsets.bottom + mFixedBoundsContainerInset);
+ }
+
+ // Update the apps recycler view
+ int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+ if (isRtl) {
+ mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset);
+ } else {
+ mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
+ }
+
+ // Update the search bar
+ if (hasSearchBar) {
+ LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) mSearchBarView.getLayoutParams();
+ lp.leftMargin = lp.rightMargin = inset;
+ }
+ }
+
+ /**
+ * Update the background of the Apps view and children.
+ */
+ private void updateBackgrounds() {
+ int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+ boolean hasSearchBar = (mSearchBarView != null) &&
+ (mSearchBarView.getVisibility() == View.VISIBLE);
+
+ // Update the background of the reveal view and list to be inset with the fixed bound
+ // insets instead of the default insets
+ mAppsRecyclerView.setBackground(new InsetDrawable(
+ getContext().getResources().getDrawable(
+ hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
+ inset, 0, inset, 0));
+ getRevealView().setBackground(new InsetDrawable(
+ getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
+ inset, 0, inset, 0));
+ }
+}
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
deleted file mode 100644
index 9e7e523e0..000000000
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ /dev/null
@@ -1,1574 +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.launcher3;
-
-import android.animation.AnimatorSet;
-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.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-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.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.GridLayout;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * 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<Object> l, int cw, int ch, AsyncTaskCallback bgR,
- AsyncTaskCallback postR, WidgetPreviewLoader w) {
- page = p;
- items = l;
- generatedImages = new ArrayList<Bitmap>();
- maxImageWidth = cw;
- maxImageHeight = ch;
- doInBackgroundCallback = bgR;
- postExecuteCallback = postR;
- widgetPreviewLoader = w;
- }
- void cleanup(boolean cancelled) {
- // Clean up any references to source/generated bitmaps
- if (generatedImages != null) {
- if (cancelled) {
- for (int i = 0; i < generatedImages.size(); i++) {
- widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i));
- }
- }
- generatedImages.clear();
- }
- }
- int page;
- ArrayList<Object> items;
- ArrayList<Bitmap> sourceImages;
- ArrayList<Bitmap> generatedImages;
- int maxImageWidth;
- int maxImageHeight;
- AsyncTaskCallback doInBackgroundCallback;
- AsyncTaskCallback postExecuteCallback;
- WidgetPreviewLoader widgetPreviewLoader;
-}
-
-/**
- * A generic template for an async task used in AppsCustomize.
- */
-class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
- 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;
-}
-
-/**
- * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
- */
-public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
- View.OnClickListener, View.OnKeyListener, DragSource,
- PagedViewWidget.ShortPressListener, LauncherTransitionable {
- static final String TAG = "AppsCustomizePagedView";
-
- private static Rect sTmpRect = new Rect();
-
- /**
- * The different content types that this paged view can show.
- */
- public enum ContentType {
- Applications,
- Widgets
- }
- private ContentType mContentType = ContentType.Applications;
-
- // Refs
- private Launcher mLauncher;
- private DragController mDragController;
- private final LayoutInflater mLayoutInflater;
- private final PackageManager mPackageManager;
-
- // Save and Restore
- private int mSaveInstanceStateItemIndex = -1;
-
- // Content
- private ArrayList<AppInfo> mApps;
- private ArrayList<Object> mWidgets;
-
- // Caching
- private IconCache mIconCache;
-
- // Dimens
- private int mContentWidth, mContentHeight;
- private int mWidgetCountX, mWidgetCountY;
- private PagedViewCellLayout mWidgetSpacingLayout;
- private int mNumAppsPages;
- private int mNumWidgetPages;
- private Rect mAllAppsPadding = new Rect();
-
- // Previews & outlines
- ArrayList<AppsCustomizeAsyncTask> 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;
- boolean mPageBackgroundsVisible = true;
-
- private Toast mWidgetInstructionToast;
-
- // Deferral of loading widget previews during launcher transitions
- private boolean mInTransition;
- private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
- new ArrayList<AsyncTaskPageData>();
- private ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
- new ArrayList<Runnable>();
-
- WidgetPreviewLoader mWidgetPreviewLoader;
-
- private boolean mInBulkBind;
- private boolean mNeedToUpdatePageCountsAndInvalidateData;
-
- public AppsCustomizePagedView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mLayoutInflater = LayoutInflater.from(context);
- mPackageManager = context.getPackageManager();
- mApps = new ArrayList<AppInfo>();
- mWidgets = new ArrayList<Object>();
- mIconCache = (LauncherAppState.getInstance()).getIconCache();
- mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
-
- // Save the default widget preview background
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
- mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
- mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
- 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);
- }
- setSinglePageInViewport();
- }
-
- @Override
- protected void init() {
- super.init();
- mCenterPagesVertically = false;
-
- Context context = getContext();
- Resources r = context.getResources();
- setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
- }
-
- public void onFinishInflate() {
- super.onFinishInflate();
-
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- setPadding(grid.edgeMarginPx, 2 * grid.edgeMarginPx,
- grid.edgeMarginPx, 2 * grid.edgeMarginPx);
- }
-
- void setAllAppsPadding(Rect r) {
- mAllAppsPadding.set(r);
- }
-
- void setWidgetsPageIndicatorPadding(int pageIndicatorHeight) {
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), pageIndicatorHeight);
- }
-
- WidgetPreviewLoader getWidgetPreviewLoader() {
- if (mWidgetPreviewLoader == null) {
- mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
- }
- return mWidgetPreviewLoader;
- }
-
- /** 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 (mContentType == ContentType.Applications) {
- AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(currentPage);
- ShortcutAndWidgetContainer childrenLayout = layout.getShortcutsAndWidgets();
- int numItemsPerPage = mCellCountX * mCellCountY;
- int childCount = childrenLayout.getChildCount();
- if (childCount > 0) {
- i = (currentPage * numItemsPerPage) + (childCount / 2);
- }
- } else if (mContentType == ContentType.Widgets) {
- int numApps = mApps.size();
- PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
- int numItemsPerPage = mWidgetCountX * mWidgetCountY;
- int childCount = layout.getChildCount();
- if (childCount > 0) {
- i = numApps +
- (currentPage * numItemsPerPage) + (childCount / 2);
- }
- } else {
- throw new RuntimeException("Invalid ContentType");
- }
- }
- 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 (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) {
- // Now that the data is ready, we can calculate the content width, the number of cells to
- // use for each page
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- mCellCountX = (int) grid.allAppsNumCols;
- mCellCountY = (int) grid.allAppsNumRows;
- updatePageCounts();
-
- // Force a measure to update recalculate the gaps
- mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
- mContentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
- int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
- int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
- mWidgetSpacingLayout.measure(widthSpec, heightSpec);
-
- final boolean hostIsTransitioning = getTabHost().isInTransition();
- int page = getPageForComponent(mSaveInstanceStateItemIndex);
- invalidatePageData(Math.max(0, page), hostIsTransitioning);
- }
-
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- if (!isDataReady()) {
- if ((LauncherAppState.isDisableAllApps() || !mApps.isEmpty()) && !mWidgets.isEmpty()) {
- post(new Runnable() {
- // This code triggers requestLayout so must be posted outside of the
- // layout pass.
- public void run() {
- if (Utilities.isViewAttachedToWindow(AppsCustomizePagedView.this)) {
- setDataIsReady();
- onDataReady(getMeasuredWidth(), getMeasuredHeight());
- }
- }
- });
- }
- }
- }
-
- public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) {
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
- // Get the list of widgets and shortcuts
- mWidgets.clear();
- for (Object o : widgetsAndShortcuts) {
- if (o instanceof LauncherAppWidgetProviderInfo) {
- LauncherAppWidgetProviderInfo widget = (LauncherAppWidgetProviderInfo) o;
- if (!app.shouldShowAppOrWidgetProvider(widget.provider) && !widget.isCustomWidget) {
- continue;
- }
-
- if (widget.minSpanX > 0 && widget.minSpanY > 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 <= (int) grid.numColumns &&
- minSpanY <= (int) grid.numRows) {
- 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 + ")");
- }
- } else {
- // just add shortcuts
- mWidgets.add(o);
- }
- }
-
- updatePageCountsAndInvalidateData();
- }
-
- public void setBulkBind(boolean bulkBind) {
- if (bulkBind) {
- mInBulkBind = true;
- } else {
- mInBulkBind = false;
- if (mNeedToUpdatePageCountsAndInvalidateData) {
- updatePageCountsAndInvalidateData();
- }
- }
- }
-
- private void updatePageCountsAndInvalidateData() {
- if (mInBulkBind) {
- mNeedToUpdatePageCountsAndInvalidateData = true;
- } else {
- updatePageCounts();
- invalidateOnDataChange();
- mNeedToUpdatePageCountsAndInvalidateData = false;
- }
- }
-
- @Override
- public void onClick(View v) {
- // When we have exited all apps or are in transition, disregard clicks
- if (!mLauncher.isAllAppsVisible()
- || mLauncher.getWorkspace().isSwitchingState()
- || !(v instanceof PagedViewWidget)) return;
-
- // Let the user know that they have to long press to add a widget
- if (mWidgetInstructionToast != null) {
- mWidgetInstructionToast.cancel();
- }
- mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
- Toast.LENGTH_SHORT);
- mWidgetInstructionToast.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 = LauncherAnimUtils.createAnimatorSet();
- ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
- tyuAnim.setDuration(125);
- ValueAnimator tydAnim = LauncherAnimUtils.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().beginDragShared(v, this);
- }
-
- static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
- Bundle options = null;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, sTmpRect);
- Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
- info.componentName, null);
-
- float density = launcher.getResources().getDisplayMetrics().density;
- int xPaddingDips = (int) ((padding.left + padding.right) / density);
- int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
-
- options = new Bundle();
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
- sTmpRect.left - xPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
- sTmpRect.top - yPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
- sTmpRect.right - xPaddingDips);
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
- sTmpRect.bottom - yPaddingDips);
- }
- return options;
- }
-
- private void preloadWidget(final PendingAddWidgetInfo info) {
- final LauncherAppWidgetProviderInfo pInfo = info.info;
- final Bundle options = pInfo.isCustomWidget ? null :
- getDefaultOptionsForWidget(mLauncher, info);
-
- if (pInfo.configure != null) {
- info.bindOptions = options;
- return;
- }
-
- mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
- mBindWidgetRunnable = new Runnable() {
- @Override
- public void run() {
- if (pInfo.isCustomWidget) {
- mWidgetCleanupState = WIDGET_BOUND;
- return;
- }
-
- mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
- if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
- mWidgetLoadingId, pInfo, options)) {
- mWidgetCleanupState = WIDGET_BOUND;
- }
-
- }
- };
- post(mBindWidgetRunnable);
-
- mInflateWidgetRunnable = new Runnable() {
- @Override
- public void run() {
- if (mWidgetCleanupState != WIDGET_BOUND) {
- return;
- }
- 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 && !info.isCustomWidget()) {
- 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 && !info.isCustomWidget()) {
- 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;
- Point previewPadding = null;
-
- 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]);
-
- int[] previewSizeBeforeScale = new int[1];
- preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
- spanX, spanY, maxWidth, maxHeight, null, previewSizeBeforeScale);
-
- // Compare the size of the drag preview to the preview in the AppsCustomize tray
- int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
- getWidgetPreviewLoader().maxWidthForWidgetPreview(spanX));
- scale = previewWidthInAppsCustomize / (float) preview.getWidth();
-
- // The bitmap in the AppsCustomize tray is always the the same size, so there
- // might be extra pixels around the preview itself - this accounts for that
- if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
- int padding =
- (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
- previewPadding = new Point(padding, 0);
- }
- } else {
- PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
- Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
- preview = Utilities.createIconBitmap(icon, mLauncher);
- createItemInfo.spanX = createItemInfo.spanY = 1;
- }
-
- // Don't clip alpha values for the drag outline if we're using the default widget preview
- boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
- (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
-
- // Save the preview for the outline generation, then dim the preview
- outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
- false);
-
- // Start the drag
- mLauncher.lockScreenOrientation();
- mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
- mDragController.startDrag(image, preview, this, createItemInfo,
- DragController.DRAG_ACTION_COPY, previewPadding, scale);
- outline.recycle();
- preview.recycle();
- return true;
- }
-
- @Override
- protected boolean beginDragging(final View v) {
- if (!super.beginDragging(v)) return false;
-
- if (v instanceof BubbleTextView) {
- 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()) {
- // 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) && !(target instanceof Folder))) {
- // Exit spring loaded mode if we have not successfully dropped or have not handled the
- // drop in Workspace
- mLauncher.exitSpringLoadedDragModeDelayed(true,
- Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
- mLauncher.unlockScreenOrientation(false);
- } else {
- mLauncher.unlockScreenOrientation(false);
- }
- }
-
- @Override
- public View getContent() {
- if (getChildCount() > 0) {
- return getChildAt(0);
- }
- 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, false);
- }
- 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
- public boolean supportsAppInfoDropTarget() {
- return true;
- }
-
- @Override
- public boolean supportsDeleteDropTarget() {
- return false;
- }
-
- @Override
- public float getIntrinsicIconScaleFactor() {
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- return (float) grid.allAppsIconSizePx / grid.iconSizePx;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- cancelAllTasks();
- }
-
- @Override
- public void trimMemory() {
- super.trimMemory();
- clearAllWidgetPages();
- }
-
- 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<AppsCustomizeAsyncTask> 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) {
- // Widgets appear to be cleared every time you leave, always force invalidate for them
- if (mContentType != type || type == ContentType.Widgets) {
- int page = (mContentType != type) ? 0 : getCurrentPage();
- mContentType = type;
- invalidatePageData(page, true);
- }
- }
-
- public ContentType getContentType() {
- return mContentType;
- }
-
- protected void snapToPage(int whichPage, int delta, int duration) {
- super.snapToPage(whichPage, delta, duration);
-
- // Update the thread priorities given the direction lookahead
- Iterator<AppsCustomizeAsyncTask> 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);
- }
- }
- }
-
- /*
- * 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(AppsCustomizeCellLayout layout) {
- layout.setGridSize(mCellCountX, mCellCountY);
-
- // 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(mContentWidth, MeasureSpec.AT_MOST);
- int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
- layout.measure(widthSpec, heightSpec);
-
- Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
- if (bg != null) {
- bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
- layout.setBackground(bg);
- }
-
- setVisibilityOnChildren(layout, View.VISIBLE);
- }
-
- public void setPageBackgroundsVisible(boolean visible) {
- mPageBackgroundsVisible = visible;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; ++i) {
- Drawable bg = getChildAt(i).getBackground();
- if (bg != null) {
- bg.setAlpha(visible ? 255 : 0);
- }
- }
- }
-
- public void syncAppsPageItems(int page, boolean immediate) {
- // ensure that we have the right number of items on the pages
- final boolean isRtl = isLayoutRtl();
- int numCells = mCellCountX * mCellCountY;
- int startIndex = page * numCells;
- int endIndex = Math.min(startIndex + numCells, mApps.size());
- AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);
-
- layout.removeAllViewsOnPage();
- ArrayList<Object> items = new ArrayList<Object>();
- ArrayList<Bitmap> images = new ArrayList<Bitmap>();
- for (int i = startIndex; i < endIndex; ++i) {
- AppInfo info = mApps.get(i);
- BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
- R.layout.apps_customize_application, layout, false);
- icon.applyFromApplicationInfo(info);
- icon.setOnClickListener(mLauncher);
- icon.setOnLongClickListener(this);
- icon.setOnTouchListener(this);
- icon.setOnKeyListener(this);
- icon.setOnFocusChangeListener(layout.mFocusHandlerView);
-
- int index = i - startIndex;
- int x = index % mCellCountX;
- int y = index / mCellCountX;
- if (isRtl) {
- x = mCellCountX - x - 1;
- }
- layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
-
- items.add(info);
- images.add(info.iconBitmap);
- }
-
- enableHwLayersOnVisiblePages();
- }
-
- /**
- * 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<AppsCustomizeAsyncTask> 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<Object> widgets,
- int cellWidth, int cellHeight, int cellCountX) {
-
- // Prune all tasks that are no longer needed
- Iterator<AppsCustomizeAsyncTask> 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, false);
- }
- }, getWidgetPreviewLoader());
-
- // 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) {
- // 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(mContentWidth, MeasureSpec.AT_MOST);
- int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
-
- Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel_dark);
- if (bg != null) {
- bg.setAlpha(mPageBackgroundsVisible ? 255 : 0);
- layout.setBackground(bg);
- }
- layout.measure(widthSpec, heightSpec);
- }
-
- public void syncWidgetPageItems(final int page, final boolean immediate) {
- int numItemsPerPage = mWidgetCountX * mWidgetCountY;
-
- final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
-
- // Calculate the dimensions of each cell we are giving to each widget
- final ArrayList<Object> items = new ArrayList<Object>();
- int contentWidth = mContentWidth - layout.getPaddingLeft() - layout.getPaddingRight();
- final int cellWidth = contentWidth / mWidgetCountX;
- int contentHeight = mContentHeight - layout.getPaddingTop() - layout.getPaddingBottom();
-
- final int cellHeight = contentHeight / mWidgetCountY;
-
- // Prepare the set of widgets to load previews for in the background
- int offset = page * 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
- 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 LauncherAppWidgetProviderInfo) {
- // Fill in the widget information
- LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) rawInfo;
- createItemInfo = new PendingAddWidgetInfo(info, null);
-
- widget.applyFromAppWidgetProviderInfo(info, -1, getWidgetPreviewLoader());
- 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, getWidgetPreviewLoader());
- 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;
-
- if (ix > 0) {
- View border = widget.findViewById(R.id.left_border);
- border.setVisibility(View.VISIBLE);
- }
- if (ix < mWidgetCountX - 1) {
- View border = widget.findViewById(R.id.right_border);
- border.setVisibility(View.VISIBLE);
- }
-
- GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
- GridLayout.spec(iy, GridLayout.START),
- GridLayout.spec(ix, GridLayout.TOP));
- lp.width = cellWidth;
- lp.height = cellHeight;
- lp.setGravity(Gravity.TOP | Gravity.START);
- 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];
- }
-
- getWidgetPreviewLoader().setPreviewSize(
- maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
- if (immediate) {
- AsyncTaskPageData data = new AsyncTaskPageData(page, items,
- maxPreviewWidth, maxPreviewHeight, null, null, getWidgetPreviewLoader());
- loadWidgetPreviewsInBackground(null, data);
- onSyncWidgetPageItems(data, immediate);
- } else {
- if (mInTransition) {
- mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
- } else {
- prepareLoadWidgetPreviewsTask(page, items,
- maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
- }
- }
- layout.setOnLayoutListener(null);
- }
- });
- }
- 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<Object> items = data.items;
- ArrayList<Bitmap> 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();
- }
-
- images.add(getWidgetPreviewLoader().getPreview(items.get(i)));
- }
- }
-
- private void onSyncWidgetPageItems(AsyncTaskPageData data, boolean immediatelySyncItems) {
- if (!immediatelySyncItems && mInTransition) {
- mDeferredSyncWidgetPageItems.add(data);
- return;
- }
- try {
- int page = data.page;
- PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
-
- ArrayList<Object> 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);
- }
- }
-
- enableHwLayersOnVisiblePages();
-
- // Update all thread priorities
- Iterator<AppsCustomizeAsyncTask> 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() {
- disablePagedViewAnimations();
-
- removeAllViews();
- cancelAllTasks();
-
- Context context = getContext();
- if (mContentType == ContentType.Applications) {
- for (int i = 0; i < mNumAppsPages; ++i) {
- AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
- setupPage(layout);
- addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- }
- } else if (mContentType == ContentType.Widgets) {
- 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));
- }
- } else {
- throw new RuntimeException("Invalid ContentType");
- }
-
- enablePagedViewAnimations();
- }
-
- @Override
- public void syncPageItems(int page, boolean immediate) {
- if (mContentType == ContentType.Widgets) {
- syncWidgetPageItems(page, immediate);
- } else {
- syncAppsPageItems(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);
- enableHwLayersOnVisiblePages();
- }
-
- private void enableHwLayersOnVisiblePages() {
- final int screenCount = getChildCount();
-
- getVisiblePages(mTempVisiblePagesRange);
- int leftScreen = mTempVisiblePagesRange[0];
- int rightScreen = mTempVisiblePagesRange[1];
- int forceDrawScreen = -1;
- if (leftScreen == rightScreen) {
- // make sure we're caching at least two pages always
- if (rightScreen < screenCount - 1) {
- rightScreen++;
- forceDrawScreen = rightScreen;
- } else if (leftScreen > 0) {
- leftScreen--;
- forceDrawScreen = leftScreen;
- }
- } else {
- forceDrawScreen = leftScreen + 1;
- }
-
- for (int i = 0; i < screenCount; i++) {
- final View layout = (View) getPageAt(i);
- if (!(leftScreen <= i && i <= rightScreen &&
- (i == forceDrawScreen || shouldDrawChild(layout)))) {
- layout.setLayerType(LAYER_TYPE_NONE, null);
- }
- }
-
- for (int i = 0; i < screenCount; i++) {
- final View layout = (View) getPageAt(i);
-
- if (leftScreen <= i && i <= rightScreen &&
- (i == forceDrawScreen || shouldDrawChild(layout))) {
- if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
- layout.setLayerType(LAYER_TYPE_HARDWARE, null);
- }
- }
- }
- }
-
- protected void overScroll(float amount) {
- dampedOverScroll(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
- */
- public void setup(Launcher launcher, DragController dragController) {
- mLauncher = launcher;
- mDragController = dragController;
- }
-
- /**
- * 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();
- }
- }
-
- public void setApps(ArrayList<AppInfo> list) {
- if (!LauncherAppState.isDisableAllApps()) {
- mApps = list;
- Collections.sort(mApps, LauncherModel.getAppNameComparator());
- updatePageCountsAndInvalidateData();
- }
- }
-
- public ArrayList<AppInfo> getApps() {
- return mApps;
- }
-
- private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) {
- // We add it in place, in alphabetical order
- int count = list.size();
- for (int i = 0; i < count; ++i) {
- AppInfo info = list.get(i);
- int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
- if (index < 0) {
- mApps.add(-(index + 1), info);
- }
- }
- }
- public void addApps(ArrayList<AppInfo> list) {
- if (!LauncherAppState.isDisableAllApps()) {
- addAppsWithoutInvalidate(list);
- updatePageCountsAndInvalidateData();
- }
- }
- private int findAppByComponent(List<AppInfo> list, AppInfo item) {
- ComponentName removeComponent = item.intent.getComponent();
- int length = list.size();
- for (int i = 0; i < length; ++i) {
- AppInfo info = list.get(i);
- if (info.user.equals(item.user)
- && info.intent.getComponent().equals(removeComponent)) {
- return i;
- }
- }
- return -1;
- }
- private void removeAppsWithoutInvalidate(ArrayList<AppInfo> 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) {
- AppInfo info = list.get(i);
- int removeIndex = findAppByComponent(mApps, info);
- if (removeIndex > -1) {
- mApps.remove(removeIndex);
- }
- }
- }
- public void removeApps(ArrayList<AppInfo> appInfos) {
- if (!LauncherAppState.isDisableAllApps()) {
- removeAppsWithoutInvalidate(appInfos);
- updatePageCountsAndInvalidateData();
- }
- }
- public void updateApps(ArrayList<AppInfo> 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.
- if (!LauncherAppState.isDisableAllApps()) {
- removeAppsWithoutInvalidate(list);
- addAppsWithoutInvalidate(list);
- updatePageCountsAndInvalidateData();
- }
- }
-
- public void reset() {
- // If we have reset, then we should not continue to restore the previous state
- mSaveInstanceStateItemIndex = -1;
-
- if (mContentType != ContentType.Applications) {
- setContentType(ContentType.Applications);
- }
-
- if (mCurrentPage != 0) {
- invalidatePageData(0);
- }
- }
-
- private AppsCustomizeTabHost getTabHost() {
- return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
- }
-
- public void dumpState() {
- // TODO: Dump information related to current list of Applications, Widgets, etc.
- AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
- dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
- }
-
- private void dumpAppWidgetProviderInfoList(String tag, String label,
- ArrayList<Object> 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);
- }
- }
- }
-
- 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();
- }
-
- /*
- * 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;
- }
-
- protected String getCurrentPageDescription() {
- int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
- int stringId = R.string.default_scroll_format;
- int count = 0;
-
- if (mContentType == ContentType.Applications) {
- stringId = R.string.apps_customize_apps_scroll_format;
- count = mNumAppsPages;
- } else if (mContentType == ContentType.Widgets) {
- stringId = R.string.apps_customize_widgets_scroll_format;
- count = mNumWidgetPages;
- } else {
- throw new RuntimeException("Invalid ContentType");
- }
-
- return String.format(getContext().getString(stringId), page + 1, count);
- }
-}
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
deleted file mode 100644
index a2717126d..000000000
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ /dev/null
@@ -1,220 +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.launcher3;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.FrameLayout;
-
-public class AppsCustomizeTabHost extends FrameLayout implements LauncherTransitionable, Insettable {
- static final String LOG_TAG = "AppsCustomizeTabHost";
-
- private static final String APPS_TAB_TAG = "APPS";
- private static final String WIDGETS_TAB_TAG = "WIDGETS";
-
- private AppsCustomizePagedView mPagedView;
- private View mContent;
- private boolean mInTransition = false;
-
- private final Rect mInsets = new Rect();
-
- public AppsCustomizeTabHost(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- /**
- * 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).
- */
- void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) {
- mPagedView.setContentType(type);
- }
-
- public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) {
- setContentTypeImmediate(type);
- }
-
- @Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- LayoutParams flp = (LayoutParams) mContent.getLayoutParams();
- flp.topMargin = insets.top;
- flp.bottomMargin = insets.bottom;
- flp.leftMargin = insets.left;
- flp.rightMargin = insets.right;
- mContent.setLayoutParams(flp);
- }
-
- /**
- * Setup the tab host and create all necessary tabs.
- */
- @Override
- protected void onFinishInflate() {
- mPagedView = (AppsCustomizePagedView) findViewById(R.id.apps_customize_pane_content);
- mContent = findViewById(R.id.content);
- }
-
- public String getContentTag() {
- return getTabTagForContentType(mPagedView.getContentType());
- }
-
- /**
- * 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() {
- // Reset immediately
- mPagedView.reset();
- }
-
- void trimMemory() {
- mPagedView.trimMemory();
- }
-
- 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
- mPagedView.loadAssociatedPages(mPagedView.getCurrentPage(), true);
- mPagedView.loadAssociatedPages(mPagedView.getCurrentPage());
- }
- }
- @Override
- public ViewGroup getContent() {
- return mPagedView;
- }
-
- public boolean isInTransition() {
- return mInTransition;
- }
-
- /* LauncherTransitionable overrides */
- @Override
- public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
- mPagedView.onLauncherTransitionPrepare(l, animated, toWorkspace);
- mInTransition = true;
-
- if (toWorkspace) {
- // Going from All Apps -> Workspace
- setVisibilityOfSiblingsWithLowerZOrder(VISIBLE);
- } 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)
- // TODO: revisit this
- mPagedView.loadAssociatedPages(mPagedView.getCurrentPage());
- }
- }
-
- @Override
- public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
- mPagedView.onLauncherTransitionStart(l, animated, toWorkspace);
- }
-
- @Override
- public void onLauncherTransitionStep(Launcher l, float t) {
- mPagedView.onLauncherTransitionStep(l, t);
- }
-
- @Override
- public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
- mPagedView.onLauncherTransitionEnd(l, animated, toWorkspace);
- mInTransition = false;
-
- if (!toWorkspace) {
- // Make sure adjacent pages are loaded (we wait until after the transition to
- // prevent slowing down the animation)
- mPagedView.loadAssociatedPages(mPagedView.getCurrentPage());
-
- // Opening apps, need to announce what page we are on.
- AccessibilityManager am = (AccessibilityManager)
- getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
- if (am.isEnabled()) {
- // Notify the user when the page changes
- announceForAccessibility(mPagedView.getCurrentPageDescription());
- }
-
- // Going from Workspace -> All Apps
- // NOTE: We should do this at the end since we check visibility state in some of the
- // cling initialization/dismiss code above.
- setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE);
- }
- }
-
- private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) {
- ViewGroup parent = (ViewGroup) getParent();
- if (parent == null) return;
-
- View overviewPanel = ((Launcher) getContext()).getOverviewPanel();
- 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 || child == overviewPanel) {
- continue;
- }
- child.setVisibility(visibility);
- }
- }
- } else {
- throw new RuntimeException("Failed; can't get z-order of views");
- }
- }
-}
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
new file mode 100644
index 000000000..954c59ffc
--- /dev/null
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -0,0 +1,258 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.launcher3.util.Thunk;
+
+import java.util.List;
+
+
+/**
+ * The grid view adapter of all the apps.
+ */
+class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
+
+ public static final String TAG = "AppsGridAdapter";
+
+ private static final int SECTION_BREAK_VIEW_TYPE = 0;
+ private static final int ICON_VIEW_TYPE = 1;
+ private static final int EMPTY_VIEW_TYPE = 2;
+
+ /**
+ * ViewHolder for each icon.
+ */
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public View mContent;
+ public boolean mIsSectionRow;
+ public boolean mIsEmptyRow;
+
+ public ViewHolder(View v, boolean isSectionRow, boolean isEmptyRow) {
+ super(v);
+ mContent = v;
+ mIsSectionRow = isSectionRow;
+ mIsEmptyRow = isEmptyRow;
+ }
+ }
+
+ /**
+ * Helper class to size the grid items.
+ */
+ public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
+ @Override
+ public int getSpanSize(int position) {
+ if (mApps.hasNoFilteredResults()) {
+ // Empty view spans full width
+ return mAppsPerRow;
+ }
+
+ if (mApps.getAdapterItems().get(position).isSectionHeader) {
+ // Section break spans full width
+ return mAppsPerRow;
+ } else {
+ return 1;
+ }
+ }
+ }
+
+ /**
+ * Helper class to draw the section headers
+ */
+ public class GridItemDecoration extends RecyclerView.ItemDecoration {
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+ if (items.isEmpty()) {
+ return;
+ }
+
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
+ if (holder != null) {
+ GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+ child.getLayoutParams();
+ if (!holder.mIsSectionRow && !holder.mIsEmptyRow && !lp.isItemRemoved()) {
+ if (items.get(holder.getPosition() - 1).isSectionHeader) {
+ // Draw at the parent
+ AlphabeticalAppsList.AdapterItem item =
+ items.get(holder.getPosition());
+ String section = item.sectionName;
+ mSectionTextPaint.getTextBounds(section, 0, section.length(),
+ mTmpBounds);
+ if (mIsRtl) {
+ int left = parent.getWidth() - mPaddingStart - mStartMargin;
+ c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
+ child.getTop() + (2 * child.getPaddingTop()) +
+ mTmpBounds.height(), mSectionTextPaint);
+ } else {
+ int left = mPaddingStart;
+ c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
+ child.getTop() + (2 * child.getPaddingTop()) +
+ mTmpBounds.height(), mSectionTextPaint);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ // Do nothing
+ }
+ }
+
+ private LayoutInflater mLayoutInflater;
+ @Thunk AlphabeticalAppsList mApps;
+ private GridLayoutManager mGridLayoutMgr;
+ private GridSpanSizer mGridSizer;
+ private GridItemDecoration mItemDecoration;
+ private View.OnTouchListener mTouchListener;
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+ @Thunk int mAppsPerRow;
+ @Thunk boolean mIsRtl;
+ private String mEmptySearchText;
+
+ // Section drawing
+ @Thunk int mPaddingStart;
+ @Thunk int mStartMargin;
+ @Thunk Paint mSectionTextPaint;
+ @Thunk Rect mTmpBounds = new Rect();
+
+
+ public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
+ View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
+ View.OnLongClickListener iconLongClickListener) {
+ Resources res = context.getResources();
+ mApps = apps;
+ mAppsPerRow = appsPerRow;
+ mGridSizer = new GridSpanSizer();
+ mGridLayoutMgr = new GridLayoutManager(context, appsPerRow, GridLayoutManager.VERTICAL,
+ false);
+ mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
+ mItemDecoration = new GridItemDecoration();
+ mLayoutInflater = LayoutInflater.from(context);
+ mTouchListener = touchListener;
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+ mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset);
+ mSectionTextPaint = new Paint();
+ mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.apps_view_section_text_size));
+ mSectionTextPaint.setColor(res.getColor(R.color.apps_view_section_text_color));
+ mSectionTextPaint.setAntiAlias(true);
+ }
+
+ /**
+ * Sets the number of apps per row.
+ */
+ public void setNumAppsPerRow(int appsPerRow) {
+ mAppsPerRow = appsPerRow;
+ mGridLayoutMgr.setSpanCount(appsPerRow);
+ }
+
+ /**
+ * Sets whether we are in RTL mode.
+ */
+ public void setRtl(boolean rtl) {
+ mIsRtl = rtl;
+ }
+
+ /**
+ * Sets the text to show when there are no apps.
+ */
+ public void setEmptySearchText(String query) {
+ mEmptySearchText = query;
+ }
+
+ /**
+ * Returns the grid layout manager.
+ */
+ public GridLayoutManager getLayoutManager(Context context) {
+ return mGridLayoutMgr;
+ }
+
+ /**
+ * Returns the item decoration for the recycler view.
+ */
+ public RecyclerView.ItemDecoration getItemDecoration() {
+ return mItemDecoration;
+ }
+
+ /**
+ * Returns the left padding for the recycler view.
+ */
+ public int getContentMarginStart() {
+ return mStartMargin;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case EMPTY_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
+ false), false /* isSectionRow */, true /* isEmptyRow */);
+ case SECTION_BREAK_VIEW_TYPE:
+ return new ViewHolder(new View(parent.getContext()), true /* isSectionRow */,
+ false /* isEmptyRow */);
+ case ICON_VIEW_TYPE:
+ BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
+ R.layout.apps_grid_row_icon_view, parent, false);
+ icon.setOnTouchListener(mTouchListener);
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
+ icon.setFocusable(true);
+ return new ViewHolder(icon, false /* isSectionRow */, false /* isEmptyRow */);
+ default:
+ throw new RuntimeException("Unexpected view type");
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ switch (holder.getItemViewType()) {
+ case ICON_VIEW_TYPE:
+ AppInfo info = mApps.getAdapterItems().get(position).appInfo;
+ BubbleTextView icon = (BubbleTextView) holder.mContent;
+ icon.applyFromApplicationInfo(info);
+ break;
+ case EMPTY_VIEW_TYPE:
+ TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
+ emptyViewText.setText(mEmptySearchText);
+ break;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mApps.hasNoFilteredResults()) {
+ // For the empty view
+ return 1;
+ }
+ return mApps.getAdapterItems().size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (mApps.hasNoFilteredResults()) {
+ return EMPTY_VIEW_TYPE;
+ } else if (mApps.getAdapterItems().get(position).isSectionHeader) {
+ return SECTION_BREAK_VIEW_TYPE;
+ }
+ return ICON_VIEW_TYPE;
+ }
+}
diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java
new file mode 100644
index 000000000..ffd309261
--- /dev/null
+++ b/src/com/android/launcher3/AppsListAdapter.java
@@ -0,0 +1,143 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * The linear list view adapter for all the apps.
+ */
+class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
+
+ /**
+ * ViewHolder for each row.
+ */
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public View mContent;
+
+ public ViewHolder(View v) {
+ super(v);
+ mContent = v;
+ }
+ }
+
+ private static final int SECTION_BREAK_VIEW_TYPE = 0;
+ private static final int ICON_VIEW_TYPE = 1;
+ private static final int EMPTY_VIEW_TYPE = 2;
+
+ private LayoutInflater mLayoutInflater;
+ private AlphabeticalAppsList mApps;
+ private View.OnTouchListener mTouchListener;
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+ private String mEmptySearchText;
+
+ public AppsListAdapter(Context context, AlphabeticalAppsList apps,
+ View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
+ View.OnLongClickListener iconLongClickListener) {
+ mApps = apps;
+ mLayoutInflater = LayoutInflater.from(context);
+ mTouchListener = touchListener;
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ }
+
+ public RecyclerView.LayoutManager getLayoutManager(Context context) {
+ return new LinearLayoutManager(context);
+ }
+
+ /**
+ * Sets the text to show when there are no apps.
+ */
+ public void setEmptySearchText(String query) {
+ mEmptySearchText = query;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case EMPTY_VIEW_TYPE:
+ return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
+ false));
+ case SECTION_BREAK_VIEW_TYPE:
+ return new ViewHolder(new View(parent.getContext()));
+ case ICON_VIEW_TYPE:
+ // Inflate the row and all the icon children necessary
+ ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.apps_list_row_view,
+ parent, false);
+ BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
+ R.layout.apps_list_row_icon_view, row, false);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 1);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ icon.setLayoutParams(lp);
+ icon.setOnTouchListener(mTouchListener);
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
+ icon.setFocusable(true);
+ row.addView(icon);
+ return new ViewHolder(row);
+ default:
+ throw new RuntimeException("Unexpected view type");
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ switch (holder.getItemViewType()) {
+ case ICON_VIEW_TYPE:
+ AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+ ViewGroup content = (ViewGroup) holder.mContent;
+ String sectionDescription = item.sectionName;
+
+ // Bind the section header
+ boolean showSectionHeader = true;
+ if (position > 0) {
+ AlphabeticalAppsList.AdapterItem prevItem =
+ mApps.getAdapterItems().get(position - 1);
+ showSectionHeader = prevItem.isSectionHeader;
+ }
+ TextView tv = (TextView) content.findViewById(R.id.section);
+ if (showSectionHeader) {
+ tv.setText(sectionDescription);
+ tv.setVisibility(View.VISIBLE);
+ } else {
+ tv.setVisibility(View.INVISIBLE);
+ }
+
+ // Bind the icon
+ BubbleTextView icon = (BubbleTextView) content.getChildAt(1);
+ icon.applyFromApplicationInfo(item.appInfo);
+ break;
+ case EMPTY_VIEW_TYPE:
+ TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
+ emptyViewText.setText(mEmptySearchText);
+ break;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mApps.hasNoFilteredResults()) {
+ // For the empty view
+ return 1;
+ }
+ return mApps.getAdapterItems().size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (mApps.hasNoFilteredResults()) {
+ return EMPTY_VIEW_TYPE;
+ } else if (mApps.getAdapterItems().get(position).isSectionHeader) {
+ return SECTION_BREAK_VIEW_TYPE;
+ }
+ return ICON_VIEW_TYPE;
+ }
+}
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 382066094..dac79a8ef 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -37,6 +37,7 @@ import android.util.Patterns;
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -44,6 +45,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
/**
* Layout parsing code for auto installs layout
@@ -56,6 +58,11 @@ public class AutoInstallsLayout {
static final String ACTION_LAUNCHER_CUSTOMIZATION =
"android.autoinstalls.config.action.PLAY_AUTO_INSTALL";
+ /**
+ * Layout resource which also includes grid size and hotseat count, e.g., default_layout_6x6_h5
+ */
+ private static final String FORMATTED_LAYOUT_RES_WITH_HOSTEAT = "default_layout_%dx%d_h%s";
+ private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d";
private static final String LAYOUT_RES = "default_layout";
static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost,
@@ -65,15 +72,39 @@ public class AutoInstallsLayout {
if (customizationApkInfo == null) {
return null;
}
+ return get(context, customizationApkInfo.first, customizationApkInfo.second,
+ appWidgetHost, callback);
+ }
+
+ static AutoInstallsLayout get(Context context, String pkg, Resources targetRes,
+ AppWidgetHost appWidgetHost, LayoutParserCallback callback) {
+ DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+
+ // Try with grid size and hotseat count
+ String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
+ (int) grid.numColumns, (int) grid.numRows, (int) grid.numHotseatIcons);
+ int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
+
+ // Try with only grid size
+ if (layoutId == 0) {
+ Log.d(TAG, "Formatted layout: " + layoutName
+ + " not found. Trying layout without hosteat");
+ layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
+ (int) grid.numColumns, (int) grid.numRows);
+ layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
+ }
+
+ // Try the default layout
+ if (layoutId == 0) {
+ Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout");
+ layoutId = targetRes.getIdentifier(LAYOUT_RES, "xml", pkg);
+ }
- String pkg = customizationApkInfo.first;
- Resources res = customizationApkInfo.second;
- int layoutId = res.getIdentifier(LAYOUT_RES, "xml", pkg);
if (layoutId == 0) {
Log.e(TAG, "Layout definition not found in package: " + pkg);
return null;
}
- return new AutoInstallsLayout(context, appWidgetHost, callback, res, layoutId,
+ return new AutoInstallsLayout(context, appWidgetHost, callback, targetRes, layoutId,
TAG_WORKSPACE);
}
@@ -114,9 +145,9 @@ public class AutoInstallsLayout {
private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
"com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
- private final Context mContext;
- private final AppWidgetHost mAppWidgetHost;
- private final LayoutParserCallback mCallback;
+ @Thunk final Context mContext;
+ @Thunk final AppWidgetHost mAppWidgetHost;
+ protected final LayoutParserCallback mCallback;
protected final PackageManager mPackageManager;
protected final Resources mSourceRes;
@@ -125,14 +156,21 @@ public class AutoInstallsLayout {
private final int mHotseatAllAppsRank;
private final long[] mTemp = new long[2];
- private final ContentValues mValues;
- private final String mRootTag;
+ @Thunk final ContentValues mValues;
+ protected final String mRootTag;
protected SQLiteDatabase mDb;
public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
LayoutParserCallback callback, Resources res,
int layoutId, String rootTag) {
+ this(context, appWidgetHost, callback, res, layoutId, rootTag,
+ LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile().hotseatAllAppsRank);
+ }
+
+ public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
+ LayoutParserCallback callback, Resources res,
+ int layoutId, String rootTag, int hotseatAllAppsRank) {
mContext = context;
mAppWidgetHost = appWidgetHost;
mCallback = callback;
@@ -143,8 +181,7 @@ public class AutoInstallsLayout {
mSourceRes = res;
mLayoutId = layoutId;
- mHotseatAllAppsRank = LauncherAppState.getInstance()
- .getDynamicGrid().getDeviceProfile().hotseatAllAppsRank;
+ mHotseatAllAppsRank = hotseatAllAppsRank;
}
/**
@@ -569,7 +606,7 @@ public class AutoInstallsLayout {
// failed to add, and less than 2 were actually added
if (folderItems.size() < 2) {
// Delete the folder
- Uri uri = Favorites.getContentUri(folderId, false);
+ Uri uri = Favorites.getContentUri(folderId);
SqlArguments args = new SqlArguments(uri, null, null);
mDb.delete(args.table, args.where, args.args);
addedId = -1;
@@ -642,7 +679,7 @@ public class AutoInstallsLayout {
long insertAndCheck(SQLiteDatabase db, ContentValues values);
}
- private static void copyInteger(ContentValues from, ContentValues to, String key) {
+ @Thunk static void copyInteger(ContentValues from, ContentValues to, String key) {
to.put(key, from.getAsInteger(key));
}
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index f9255e6bd..ae6ebba34 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -28,11 +28,14 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
+import com.android.launcher3.IconCache.IconLoadRequest;
+
/**
* 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
@@ -49,7 +52,7 @@ public class BubbleTextView extends TextView {
private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
static final float PADDING_V = 3.0f;
-
+ private Drawable mIcon;
private final Drawable mBackground;
private final CheckLongPressHelper mLongPressHelper;
private final HolographicOutlineHelper mOutlineHelper;
@@ -62,13 +65,19 @@ public class BubbleTextView extends TextView {
private float mSlop;
- private int mTextColor;
+ private final boolean mDeferShadowGenerationOnTouch;
private final boolean mCustomShadowsEnabled;
- private boolean mIsTextVisible;
+ private final boolean mLayoutHorizontal;
+ private final int mIconSize;
+ private final int mIconPaddingSize;
+ private final int mTextSize;
+ private int mTextColor;
private boolean mStayPressed;
private boolean mIgnorePressedStateChange;
+ private IconLoadRequest mIconLoadRequest;
+
public BubbleTextView(Context context) {
this(context, null, 0);
}
@@ -79,10 +88,21 @@ public class BubbleTextView extends TextView {
public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, true);
+ mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
+ mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
+ grid.allAppsIconSizePx);
+ mIconPaddingSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconPaddingOverride,
+ grid.iconDrawablePaddingPx);
+ mTextSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_textSizeOverride,
+ grid.allAppsIconTextSizePx);
+ mDeferShadowGenerationOnTouch =
+ a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
a.recycle();
if (mCustomShadowsEnabled) {
@@ -92,6 +112,12 @@ public class BubbleTextView extends TextView {
} else {
mBackground = null;
}
+
+ // If we are laying out horizontal, then center the text vertically
+ if (mLayoutHorizontal) {
+ setGravity(Gravity.CENTER_VERTICAL);
+ }
+
mLongPressHelper = new CheckLongPressHelper(this);
mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
@@ -106,9 +132,7 @@ public class BubbleTextView extends TextView {
super.onFinishInflate();
// Ensure we are using the right text size
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
@@ -119,16 +143,11 @@ public class BubbleTextView extends TextView {
public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
boolean setDefaultPadding, boolean promiseStateChanged) {
Bitmap b = info.getIcon(iconCache);
- LauncherAppState app = LauncherAppState.getInstance();
FastBitmapDrawable iconDrawable = Utilities.createIconDrawable(b);
iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
- setCompoundDrawables(null, iconDrawable, null, null);
- if (setDefaultPadding) {
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
- }
+ setIcon(iconDrawable, mIconSize, setDefaultPadding ? mIconPaddingSize : -1);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
}
@@ -141,20 +160,17 @@ public class BubbleTextView extends TextView {
}
public void applyFromApplicationInfo(AppInfo info) {
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
- Drawable topDrawable = Utilities.createIconDrawable(info.iconBitmap);
- topDrawable.setBounds(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx);
- setCompoundDrawables(null, topDrawable, null, null);
- setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
+ setIcon(Utilities.createIconDrawable(info.iconBitmap), mIconSize, mIconPaddingSize);
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
}
- setTag(info);
- }
+ // We don't need to check the info since it's not a ShortcutInfo
+ super.setTag(info);
+ // Verify high res immediately
+ verifyHighRes();
+ }
@Override
protected boolean setFrame(int left, int top, int right, int bottom) {
@@ -186,10 +202,19 @@ public class BubbleTextView extends TextView {
}
}
+ /** Returns the icon for this view. */
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ /** Returns whether the layout is horizontal. */
+ public boolean isLayoutHorizontal() {
+ return mLayoutHorizontal;
+ }
+
private void updateIconState() {
- Drawable top = getCompoundDrawables()[1];
- if (top instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) top).setPressed(isPressed() || mStayPressed);
+ if (mIcon instanceof FastBitmapDrawable) {
+ ((FastBitmapDrawable) mIcon).setPressed(isPressed() || mStayPressed);
}
}
@@ -204,7 +229,7 @@ public class BubbleTextView extends TextView {
// So that the pressed outline is visible immediately on setStayPressed(),
// we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
// to create it)
- if (mPressedBackground == null) {
+ if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
}
@@ -233,6 +258,10 @@ public class BubbleTextView extends TextView {
mStayPressed = stayPressed;
if (!stayPressed) {
mPressedBackground = null;
+ } else {
+ if (mPressedBackground == null) {
+ mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
+ }
}
// Only show the shadow effect when persistent pressed state is set.
@@ -325,10 +354,9 @@ public class BubbleTextView extends TextView {
super.onAttachedToWindow();
if (mBackground != null) mBackground.setCallback(this);
- Drawable top = getCompoundDrawables()[1];
- if (top instanceof PreloadIconDrawable) {
- ((PreloadIconDrawable) top).applyPreloaderTheme(getPreloaderTheme());
+ if (mIcon instanceof PreloadIconDrawable) {
+ ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
}
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -358,16 +386,6 @@ public class BubbleTextView extends TextView {
} else {
super.setTextColor(res.getColor(android.R.color.transparent));
}
- mIsTextVisible = visible;
- }
-
- public boolean isTextVisible() {
- return mIsTextVisible;
- }
-
- @Override
- protected boolean onSetAlpha(int alpha) {
- return true;
}
@Override
@@ -385,15 +403,13 @@ public class BubbleTextView extends TextView {
((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ?
info.getInstallProgress() : 0)) : 100;
- Drawable[] drawables = getCompoundDrawables();
- Drawable top = drawables[1];
- if (top != null) {
+ if (mIcon != null) {
final PreloadIconDrawable preloadDrawable;
- if (top instanceof PreloadIconDrawable) {
- preloadDrawable = (PreloadIconDrawable) top;
+ if (mIcon instanceof PreloadIconDrawable) {
+ preloadDrawable = (PreloadIconDrawable) mIcon;
} else {
- preloadDrawable = new PreloadIconDrawable(top, getPreloaderTheme());
- setCompoundDrawables(drawables[0], preloadDrawable, drawables[2], drawables[3]);
+ preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
+ setIcon(preloadDrawable, mIconSize, -1);
}
preloadDrawable.setLevel(progressLevel);
@@ -417,4 +433,61 @@ public class BubbleTextView extends TextView {
}
return theme;
}
+
+ /**
+ * Sets the icon for this view based on the layout direction.
+ */
+ private Drawable setIcon(Drawable icon, int iconSize, int drawablePadding) {
+ mIcon = icon;
+ if (iconSize != -1) {
+ mIcon.setBounds(0, 0, iconSize, iconSize);
+ }
+ if (mLayoutHorizontal) {
+ setCompoundDrawablesRelative(mIcon, null, null, null);
+ } else {
+ setCompoundDrawablesRelative(null, mIcon, null, null);
+ }
+ if (drawablePadding != -1) {
+ setCompoundDrawablePadding(drawablePadding);
+ }
+ return icon;
+ }
+
+ /**
+ * Applies the item info if it is same as what the view is pointing to currently.
+ */
+ public void reapplyItemInfo(final ItemInfo info) {
+ if (getTag() == info) {
+ mIconLoadRequest = null;
+ if (info instanceof AppInfo) {
+ applyFromApplicationInfo((AppInfo) info);
+ } else if (info instanceof ShortcutInfo) {
+ applyFromShortcutInfo((ShortcutInfo) info,
+ LauncherAppState.getInstance().getIconCache(), false);
+ }
+ }
+ }
+
+ /**
+ * Verifies that the current icon is high-res otherwise posts a request to load the icon.
+ */
+ public void verifyHighRes() {
+ if (mIconLoadRequest != null) {
+ mIconLoadRequest.cancel();
+ mIconLoadRequest = null;
+ }
+ if (getTag() instanceof AppInfo) {
+ AppInfo info = (AppInfo) getTag();
+ if (info.usingLowResIcon) {
+ mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
+ .updateIconInBackground(BubbleTextView.this, info);
+ }
+ } else if (getTag() instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) getTag();
+ if (info.usingLowResIcon) {
+ mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
+ .updateIconInBackground(BubbleTextView.this, info);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 019f86c21..5b399087a 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -17,19 +17,29 @@
package com.android.launcher3;
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.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
import android.widget.TextView;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Thunk;
/**
* Implements a DropTarget.
*/
-public class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener {
+public abstract class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener {
+
+ private static int DRAG_VIEW_DROP_DURATION = 285;
protected final int mTransitionDuration;
@@ -44,6 +54,9 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
/** The paint applied to the drag view on hover */
protected int mHoverColor = 0;
+ protected ColorStateList mOriginalTextColor;
+ protected TransitionDrawable mDrawable;
+
public ButtonDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -56,12 +69,37 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
mBottomDragPadding = r.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
}
- void setLauncher(Launcher launcher) {
- mLauncher = launcher;
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mOriginalTextColor = getTextColors();
+
+ // Remove the text in the Phone UI in landscape
+ int orientation = getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (!LauncherAppState.getInstance().isScreenLarge()) {
+ setText("");
+ }
+ }
}
- public boolean acceptDrop(DragObject d) {
- return false;
+ protected void setDrawable(int resId) {
+ // Get the hover color
+ mDrawable = (TransitionDrawable) getCurrentDrawable();
+
+ if (mDrawable == null) {
+ // TODO: investigate why this is ever happening. Presently only on one known device.
+ mDrawable = (TransitionDrawable) getResources().getDrawable(resId);
+ setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
+ }
+
+ if (null != mDrawable) {
+ mDrawable.setCrossFadeEnabled(true);
+ }
+ }
+
+ public void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
}
public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) {
@@ -78,37 +116,94 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
return null;
}
- public void onDrop(DragObject d) {
+ @Override
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { }
+
+ @Override
+ public final void onDragEnter(DragObject d) {
+ d.dragView.setColor(mHoverColor);
+ mDrawable.startTransition(mTransitionDuration);
+ setTextColor(mHoverColor);
}
- public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ @Override
+ public void onDragOver(DragObject d) {
// Do nothing
}
- public void onDragEnter(DragObject d) {
- d.dragView.setColor(mHoverColor);
+ protected void resetHoverColor() {
+ mDrawable.resetTransition();
+ setTextColor(mOriginalTextColor);
}
- public void onDragOver(DragObject d) {
- // Do nothing
+ @Override
+ public final void onDragExit(DragObject d) {
+ if (!d.dragComplete) {
+ d.dragView.setColor(0);
+ resetHoverColor();
+ } else {
+ // Restore the hover color
+ d.dragView.setColor(mHoverColor);
+ }
}
- public void onDragExit(DragObject d) {
- d.dragView.setColor(0);
+ @Override
+ public final void onDragStart(DragSource source, Object info, int dragAction) {
+ mActive = supportsDrop(source, info);
+ mDrawable.resetTransition();
+ setTextColor(mOriginalTextColor);
+ ((ViewGroup) getParent()).setVisibility(mActive ? View.VISIBLE : View.GONE);
}
- public void onDragStart(DragSource source, Object info, int dragAction) {
- // Do nothing
+ @Override
+ public final boolean acceptDrop(DragObject dragObject) {
+ return supportsDrop(dragObject.dragSource, dragObject.dragInfo);
}
+ protected abstract boolean supportsDrop(DragSource source, Object info);
+
+ @Override
public boolean isDropEnabled() {
return mActive;
}
+ @Override
public void onDragEnd() {
- // Do nothing
+ mActive = false;
}
+ /**
+ * On drop animate the dropView to the icon.
+ */
+ @Override
+ public void onDrop(final DragObject d) {
+ final DragLayer dragLayer = mLauncher.getDragLayer();
+ final Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+ int width = mDrawable.getIntrinsicWidth();
+ int height = mDrawable.getIntrinsicHeight();
+ final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+ width, height);
+ final float scale = (float) to.width() / from.width();
+ mSearchDropTargetBar.deferOnDragEnd();
+
+ Runnable onAnimationEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ completeDrop(d);
+ mSearchDropTargetBar.onDragEnd();
+ mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
+ }
+ };
+ dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
+ DRAG_VIEW_DROP_DURATION, new DecelerateInterpolator(2),
+ new LinearInterpolator(), onAnimationEndRunnable,
+ DragLayer.ANIMATION_END_DISAPPEAR, null);
+ }
+
+ @Thunk abstract void completeDrop(DragObject d);
+
@Override
public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
super.getHitRect(outRect);
@@ -120,10 +215,10 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
}
private boolean isRtl() {
- return (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
- Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
+ protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
DragLayer dragLayer = mLauncher.getDragLayer();
// Find the rect to animate to (the view is center aligned)
@@ -157,6 +252,7 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
return to;
}
+ @Override
public void getLocationInDragLayer(int[] loc) {
mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e6865b2e6..f4afb954d 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -22,6 +22,7 @@ import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -33,7 +34,12 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -41,35 +47,40 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LayoutAnimationController;
import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.LauncherAccessibilityDelegate.DragType;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.List;
import java.util.Stack;
public class CellLayout extends ViewGroup {
static final String TAG = "CellLayout";
private Launcher mLauncher;
- private int mCellWidth;
- private int mCellHeight;
+ @Thunk int mCellWidth;
+ @Thunk int mCellHeight;
private int mFixedCellWidth;
private int mFixedCellHeight;
- private int mCountX;
- private int mCountY;
+ @Thunk int mCountX;
+ @Thunk int mCountY;
private int mOriginalWidthGap;
private int mOriginalHeightGap;
- private int mWidthGap;
- private int mHeightGap;
+ @Thunk int mWidthGap;
+ @Thunk int mHeightGap;
private int mMaxGap;
private boolean mDropPending = false;
private boolean mIsDragTarget = true;
@@ -77,7 +88,7 @@ public class CellLayout extends ViewGroup {
// 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];
+ @Thunk final int[] mTmpPoint = new int[2];
int[] mTempLocation = new int[2];
boolean[][] mOccupied;
@@ -114,8 +125,8 @@ public class CellLayout extends ViewGroup {
// 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];
+ @Thunk Rect[] mDragOutlines = new Rect[4];
+ @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
private InterruptibleInOutAnimator[] mDragOutlineAnims =
new InterruptibleInOutAnimator[mDragOutlines.length];
@@ -125,7 +136,7 @@ public class CellLayout extends ViewGroup {
private final FastBitmapView mTouchFeedbackView;
- private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
+ @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
HashMap<CellLayout.LayoutParams, Animator>();
private HashMap<View, ReorderPreviewAnimation>
mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
@@ -156,7 +167,7 @@ public class CellLayout extends ViewGroup {
private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
private static final int REORDER_ANIMATION_DURATION = 150;
- private float mReorderPreviewAnimationMagnitude;
+ @Thunk float mReorderPreviewAnimationMagnitude;
private ArrayList<View> mIntersectingViews = new ArrayList<View>();
private Rect mOccupiedRect = new Rect();
@@ -169,6 +180,14 @@ public class CellLayout extends ViewGroup {
private final static Paint sPaint = new Paint();
+ // Related to accessible drag and drop
+ DragAndDropAccessibilityDelegate mTouchHelper = new DragAndDropAccessibilityDelegate(this);
+ private boolean mUseTouchHelper = false;
+ OnClickListener mOldClickListener = null;
+ OnClickListener mOldWorkspaceListener = null;
+ @Thunk int mDownX = 0;
+ @Thunk int mDownY = 0;
+
public CellLayout(Context context) {
this(context, null);
}
@@ -294,6 +313,301 @@ public class CellLayout extends ViewGroup {
addView(mShortcutsAndWidgets);
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public void enableAccessibleDrag(boolean enable) {
+ mUseTouchHelper = enable;
+ if (!enable) {
+ ViewCompat.setAccessibilityDelegate(this, null);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ setOnClickListener(mLauncher);
+ } else {
+ ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ setOnClickListener(mTouchHelper);
+ }
+
+ // Invalidate the accessibility hierarchy
+ if (getParent() != null) {
+ getParent().notifySubtreeAccessibilityStateChanged(
+ this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ }
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ // Always attempt to dispatch hover events to accessibility first.
+ if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mDownX = (int) event.getX();
+ mDownY = (int) event.getY();
+ }
+ return super.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mUseTouchHelper ||
+ (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
+ return true;
+ }
+ return false;
+ }
+
+ class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper implements OnClickListener {
+ private final Rect mTempRect = new Rect();
+
+ public DragAndDropAccessibilityDelegate(View forView) {
+ super(forView);
+ }
+
+ private int getViewIdAt(float x, float y) {
+ if (x < 0 || y < 0 || x > getMeasuredWidth() || y > getMeasuredHeight()) {
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ // Map coords to cell
+ int cellX = (int) Math.floor(x / (mCellWidth + mWidthGap));
+ int cellY = (int) Math.floor(y / (mCellHeight + mHeightGap));
+
+ // Map cell to id
+ int id = cellX * mCountY + cellY;
+ return id;
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ return nearestDropLocation(getViewIdAt(x, y));
+ }
+
+ protected int nearestDropLocation(int id) {
+ int count = mCountX * mCountY;
+ for (int delta = 0; delta < count; delta++) {
+ if (id + delta <= (count - 1)) {
+ int target = intersectsValidDropTarget(id + delta);
+ if (target >= 0) {
+ return target;
+ }
+ } else if (id - delta >= 0) {
+ int target = intersectsValidDropTarget(id - delta);
+ if (target >= 0) {
+ return target;
+ }
+ }
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ /**
+ * Find the virtual view id corresponding to the top left corner of any drop region by which
+ * the passed id is contained. For an icon, this is simply
+ *
+ * @param id the id we're interested examining (ie. does it fit there?)
+ * @return the view id of the top left corner of a valid drop region or -1 if there is no
+ * such valid region. For the icon, this can just be -1 or id.
+ */
+ protected int intersectsValidDropTarget(int id) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate == null) {
+ return -1;
+ }
+
+ int y = id % mCountY;
+ int x = id / mCountY;
+ LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+ if (dragInfo.dragType == DragType.WIDGET) {
+ // For a widget, every cell must be vacant. In addition, we will return any valid
+ // drop target by which the passed id is contained.
+ boolean fits = false;
+
+ // These represent the amount that we can back off if we hit a problem. They
+ // get consumed as we move up and to the right, trying new regions.
+ int spanX = dragInfo.info.spanX;
+ int spanY = dragInfo.info.spanY;
+
+ for (int m = 0; m < spanX; m++) {
+ for (int n = 0; n < spanY; n++) {
+
+ fits = true;
+ int x0 = x - m;
+ int y0 = y - n;
+
+ if (x0 < 0 || y0 < 0) continue;
+
+ for (int i = x0; i < x0 + spanX; i++) {
+ if (!fits) break;
+ for (int j = y0; j < y0 + spanY; j++) {
+ if (i >= mCountX || j >= mCountY || mOccupied[i][j]) {
+ fits = false;
+ break;
+ }
+ }
+ }
+ if (fits) {
+ return x0 * mCountY + y0;
+ }
+ }
+ }
+ return -1;
+ } else {
+ // For an icon, we simply check the view directly below
+ View child = getChildAt(x, y);
+ if (child == null || child == dragInfo.item) {
+ // Empty cell. Good for an icon or folder.
+ return id;
+ } else if (dragInfo.dragType != DragType.FOLDER) {
+ // For icons, we can consider cells that have another icon or a folder.
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (info instanceof AppInfo || info instanceof FolderInfo ||
+ info instanceof ShortcutInfo) {
+ return id;
+ }
+ }
+ return -1;
+ }
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> virtualViews) {
+ // We create a virtual view for each cell of the grid
+ // The cell ids correspond to cells in reading order.
+ int nCells = mCountX * mCountY;
+
+ for (int i = 0; i < nCells; i++) {
+ if (intersectsValidDropTarget(i) >= 0) {
+ virtualViews.add(i);
+ }
+ }
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate == null) {
+ return false;
+ }
+
+ if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+ String confirmation = getConfirmationForIconDrop(viewId);
+ delegate.handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onClick(View arg0) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate == null) {
+ return;
+ }
+
+ int viewId = getViewIdAt(mDownX, mDownY);
+
+ String confirmation = getConfirmationForIconDrop(viewId);
+ delegate.handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) {
+ if (id == ExploreByTouchHelper.INVALID_ID) {
+ throw new IllegalArgumentException("Invalid virtual view id");
+ }
+ // We're required to set something here.
+ event.setContentDescription("");
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
+ if (id == ExploreByTouchHelper.INVALID_ID) {
+ throw new IllegalArgumentException("Invalid virtual view id");
+ }
+
+ node.setContentDescription(getLocationDescriptionForIconDrop(id));
+ node.setBoundsInParent(getItemBounds(id));
+
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+ node.setClickable(true);
+ node.setFocusable(true);
+ }
+
+ private String getLocationDescriptionForIconDrop(int id) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate == null) {
+ return "";
+ }
+
+ int y = id % mCountY;
+ int x = id / mCountY;
+ LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+ Resources res = getContext().getResources();
+ View child = getChildAt(x, y);
+ if (child == null || child == dragInfo.item) {
+ return res.getString(R.string.move_to_empty_cell, x, y);
+ } else {
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+ return res.getString(R.string.create_folder_with, info.title);
+ } else if (info instanceof FolderInfo) {
+ return res.getString(R.string.add_to_folder, info.title);
+ }
+ }
+ return "";
+ }
+
+ private String getConfirmationForIconDrop(int id) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate == null) {
+ return "";
+ }
+
+ int y = id % mCountY;
+ int x = id / mCountY;
+ LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+ Resources res = getContext().getResources();
+ View child = getChildAt(x, y);
+ if (child == null || child == dragInfo.item) {
+ return res.getString(R.string.item_moved);
+ } else {
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+ return res.getString(R.string.folder_created);
+
+ } else if (info instanceof FolderInfo) {
+ return res.getString(R.string.added_to_folder);
+ }
+ }
+ return "";
+ }
+
+ private Rect getItemBounds(int id) {
+ int cellY = id % mCountY;
+ int cellX = id / mCountY;
+ int x = getPaddingLeft() + (int) (cellX * (mCellWidth + mWidthGap));
+ int y = getPaddingTop() + (int) (cellY * (mCellHeight + mHeightGap));
+
+ Rect bounds = mTempRect;
+ bounds.set(x, y, x + mCellWidth, y + mCellHeight);
+ return bounds;
+ }
+ }
+
public void enableHardwareLayer(boolean hasLayer) {
mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
}
@@ -578,11 +892,11 @@ public class CellLayout extends ViewGroup {
mInterceptTouchListener = listener;
}
- int getCountX() {
+ public int getCountX() {
return mCountX;
}
- int getCountY() {
+ public int getCountY() {
return mCountY;
}
@@ -613,11 +927,7 @@ public class CellLayout extends ViewGroup {
if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
child.setId(childId);
- if (inLayout) {
- mShortcutsAndWidgets.addView(child, index, lp, true);
- } else {
- mShortcutsAndWidgets.addView(child, index, lp, false);
- }
+ mShortcutsAndWidgets.addView(child, index, lp, inLayout);
if (markCells) markCellsAsOccupiedForView(child);
@@ -683,18 +993,6 @@ public class CellLayout extends ViewGroup {
mShortcutsAndWidgets.removeViewsInLayout(start, count);
}
- @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.
- if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
- return true;
- }
-
- return false;
- }
-
/**
* Given a point, return the cell that strictly encloses that point
* @param x X coordinate of the point
@@ -2272,7 +2570,7 @@ public class CellLayout extends ViewGroup {
}
}
- private void completeAnimationImmediately() {
+ @Thunk void completeAnimationImmediately() {
if (a != null) {
a.cancel();
}
@@ -2593,7 +2891,7 @@ public class CellLayout extends ViewGroup {
return mItemPlacementDirty;
}
- private class ItemConfiguration {
+ @Thunk class ItemConfiguration {
HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
ArrayList<View> sortedViews = new ArrayList<View>();
@@ -2727,7 +3025,7 @@ public class CellLayout extends ViewGroup {
*
* @return True if a vacant cell of the specified dimension was found, false otherwise.
*/
- boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+ public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
}
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 81149793d..10ca6a371 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -18,9 +18,11 @@ package com.android.launcher3;
import android.view.View;
+import com.android.launcher3.util.Thunk;
+
public class CheckLongPressHelper {
- private View mView;
- private boolean mHasPerformedLongPress;
+ @Thunk View mView;
+ @Thunk boolean mHasPerformedLongPress;
private CheckForLongPress mPendingCheckForLongPress;
class CheckForLongPress implements Runnable {
diff --git a/src/com/android/launcher3/CommonAppTypeParser.java b/src/com/android/launcher3/CommonAppTypeParser.java
new file mode 100644
index 000000000..31641799d
--- /dev/null
+++ b/src/com/android/launcher3/CommonAppTypeParser.java
@@ -0,0 +1,152 @@
+/*
+ * 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.launcher3;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.XmlResourceParser;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * A class that parses content values corresponding to some common app types.
+ */
+public class CommonAppTypeParser implements LayoutParserCallback {
+ private static final String TAG = "CommonAppTypeParser";
+
+ // Including TARGET_NONE
+ public static final int SUPPORTED_TYPE_COUNT = 7;
+
+ private static final int RESTORE_FLAG_BIT_SHIFT = 4;
+
+
+ private final long mItemId;
+ private final int mResId;
+ private final Context mContext;
+
+ ContentValues parsedValues;
+ Intent parsedIntent;
+ String parsedTitle;
+
+ public CommonAppTypeParser(long itemId, int itemType, Context context) {
+ mItemId = itemId;
+ mContext = context;
+ mResId = getResourceForItemType(itemType);
+ }
+
+ @Override
+ public long generateNewItemId() {
+ return mItemId;
+ }
+
+ @Override
+ public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
+ parsedValues = values;
+
+ // Remove unwanted values
+ values.put(Favorites.ICON_TYPE, (Integer) null);
+ values.put(Favorites.ICON_PACKAGE, (String) null);
+ values.put(Favorites.ICON_RESOURCE, (String) null);
+ values.put(Favorites.ICON, (byte[]) null);
+ return 1;
+ }
+
+ /**
+ * Tries to find a suitable app to the provided app type.
+ */
+ public boolean findDefaultApp() {
+ if (mResId == 0) {
+ return false;
+ }
+
+ parsedIntent = null;
+ parsedValues = null;
+ new MyLayoutParser().parseValues();
+ return (parsedValues != null) && (parsedIntent != null);
+ }
+
+ private class MyLayoutParser extends DefaultLayoutParser {
+
+ public MyLayoutParser() {
+ super(CommonAppTypeParser.this.mContext, null, CommonAppTypeParser.this,
+ CommonAppTypeParser.this.mContext.getResources(), mResId, TAG_RESOLVE, 0);
+ }
+
+ @Override
+ protected long addShortcut(String title, Intent intent, int type) {
+ if (type == Favorites.ITEM_TYPE_APPLICATION) {
+ parsedIntent = intent;
+ parsedTitle = title;
+ }
+ return super.addShortcut(title, intent, type);
+ }
+
+ public void parseValues() {
+ XmlResourceParser parser = mSourceRes.getXml(mLayoutId);
+ try {
+ beginDocument(parser, mRootTag);
+ new ResolveParser().parseAndAdd(parser);
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Unable to parse default app info", e);
+ }
+ parser.close();
+ }
+ }
+
+ public static int getResourceForItemType(int type) {
+ switch (type) {
+ case Favorite.TARGET_PHONE:
+ return R.xml.app_target_phone;
+
+ case Favorite.TARGET_MESSENGER:
+ return R.xml.app_target_messenger;
+
+ case Favorite.TARGET_EMAIL:
+ return R.xml.app_target_email;
+
+ case Favorite.TARGET_BROWSER:
+ return R.xml.app_target_browser;
+
+ case Favorite.TARGET_GALLERY:
+ return R.xml.app_target_gallery;
+
+ case Favorite.TARGET_CAMERA:
+ return R.xml.app_target_camera;
+
+ default:
+ return 0;
+ }
+ }
+
+ public static int encodeItemTypeToFlag(int itemType) {
+ return itemType << RESTORE_FLAG_BIT_SHIFT;
+ }
+
+ public static int decodeItemTypeFromFlag(int flag) {
+ return (flag & ShortcutInfo.FLAG_RESTORED_APP_TYPE) >> RESTORE_FLAG_BIT_SHIFT;
+ }
+
+}
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index 986ae81f5..7b91c675b 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -13,6 +13,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -28,15 +29,15 @@ import java.util.List;
public class DefaultLayoutParser extends AutoInstallsLayout {
private static final String TAG = "DefaultLayoutParser";
- private static final String TAG_RESOLVE = "resolve";
+ protected static final String TAG_RESOLVE = "resolve";
private static final String TAG_FAVORITES = "favorites";
- private static final String TAG_FAVORITE = "favorite";
+ protected static final String TAG_FAVORITE = "favorite";
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_PARTNER_FOLDER = "partner-folder";
- private static final String ATTR_URI = "uri";
+ protected static final String ATTR_URI = "uri";
private static final String ATTR_CONTAINER = "container";
private static final String ATTR_SCREEN = "screen";
private static final String ATTR_FOLDER_ITEMS = "folderItems";
@@ -44,7 +45,12 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
LayoutParserCallback callback, Resources sourceRes, int layoutId) {
super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
- Log.e(TAG, "Default layout parser initialized");
+ }
+
+ public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
+ LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag,
+ int hotseatAllAppsRank) {
+ super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag, hotseatAllAppsRank);
}
@Override
@@ -52,7 +58,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
return getFolderElementsMap(mSourceRes);
}
- private HashMap<String, TagParser> getFolderElementsMap(Resources res) {
+ @Thunk HashMap<String, TagParser> getFolderElementsMap(Resources res) {
HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
@@ -84,7 +90,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
/**
* AppShortcutParser which also supports adding URI based intents
*/
- private class AppShortcutWithUriParser extends AppShortcutParser {
+ @Thunk class AppShortcutWithUriParser extends AppShortcutParser {
@Override
protected long invalidPackageOrClass(XmlResourceParser parser) {
@@ -196,7 +202,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
/**
* Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
*/
- private class ResolveParser implements TagParser {
+ protected class ResolveParser implements TagParser {
private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
@@ -226,7 +232,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
/**
* A parser which adds a folder whose contents come from partner apk.
*/
- private class PartnerFolderParser implements TagParser {
+ @Thunk class PartnerFolderParser implements TagParser {
@Override
public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
@@ -252,7 +258,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
/**
* An extension of FolderParser which allows adding items from a different xml.
*/
- private class MyFolderParser extends FolderParser {
+ @Thunk class MyFolderParser extends FolderParser {
@Override
public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java
index a2d121d63..eb7c26a28 100644
--- a/src/com/android/launcher3/DeferredHandler.java
+++ b/src/com/android/launcher3/DeferredHandler.java
@@ -22,6 +22,8 @@ import android.os.Message;
import android.os.MessageQueue;
import android.util.Pair;
+import com.android.launcher3.util.Thunk;
+
import java.util.LinkedList;
import java.util.ListIterator;
@@ -33,11 +35,11 @@ import java.util.ListIterator;
* This class is fifo.
*/
public class DeferredHandler {
- private LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>();
+ @Thunk LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>();
private MessageQueue mMessageQueue = Looper.myQueue();
private Impl mHandler = new Impl();
- private class Impl extends Handler implements MessageQueue.IdleHandler {
+ @Thunk class Impl extends Handler implements MessageQueue.IdleHandler {
public void handleMessage(Message msg) {
Pair<Runnable, Integer> p;
Runnable r;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index ebe874f38..aa3e66c09 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -19,31 +19,22 @@ package com.android.launcher3;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-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.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.TransitionDrawable;
import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.UserManager;
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.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.WidgetsContainerView;
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;
@@ -51,13 +42,6 @@ public class DeleteDropTarget extends ButtonDropTarget {
private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
- private ColorStateList mOriginalTextColor;
- private TransitionDrawable mUninstallDrawable;
- private TransitionDrawable mRemoveDrawable;
- private TransitionDrawable mCurrentDrawable;
-
- private boolean mWaitingForUninstall = false;
-
public DeleteDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -69,271 +53,27 @@ public class DeleteDropTarget extends ButtonDropTarget {
@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 (!LauncherAppState.getInstance().isScreenLarge()) {
- setText("");
- }
- }
- }
-
- private boolean isAllAppsApplication(DragSource source, Object info) {
- return source.supportsAppInfoDropTarget() && (info instanceof AppInfo);
- }
- 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:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- return true;
- }
- }
- }
- return false;
- }
- private boolean isDragSourceWorkspaceOrFolder(DragObject d) {
- return (d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder);
- }
-
- private void setHoverColor() {
- if (mCurrentDrawable != null) {
- mCurrentDrawable.startTransition(mTransitionDuration);
- }
- setTextColor(mHoverColor);
- }
- private void resetHoverColor() {
- if (mCurrentDrawable != null) {
- mCurrentDrawable.resetTransition();
- }
- setTextColor(mOriginalTextColor);
- }
+ mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
- @Override
- public boolean acceptDrop(DragObject d) {
- return willAcceptDrop(d.dragInfo);
+ setDrawable(R.drawable.remove_target_selector);
}
- public static boolean willAcceptDrop(Object info) {
- if (info instanceof ItemInfo) {
- ItemInfo item = (ItemInfo) info;
- if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
- item.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET ||
- item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
- return true;
- }
-
- if (!LauncherAppState.isDisableAllApps() &&
- item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
- return true;
- }
-
- if (!LauncherAppState.isDisableAllApps() &&
- item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
- item instanceof AppInfo) {
- AppInfo appInfo = (AppInfo) info;
- return (appInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
- }
-
- if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
- item instanceof ShortcutInfo) {
- if (LauncherAppState.isDisableAllApps()) {
- ShortcutInfo shortcutInfo = (ShortcutInfo) info;
- return (shortcutInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
- } else {
- return true;
- }
- }
- }
- return false;
+ public static boolean willAcceptDrop(DragSource source, Object info) {
+ return (info instanceof ItemInfo) && source.supportsDeleteDropTarget();
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
@Override
- public void onDragStart(DragSource source, Object info, int dragAction) {
- boolean isVisible = true;
- boolean useUninstallLabel = !LauncherAppState.isDisableAllApps() &&
- isAllAppsApplication(source, info);
- boolean useDeleteLabel = !useUninstallLabel && source.supportsDeleteDropTarget();
-
- // 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.
- // Hide the delete target if it is a widget from AppsCustomize.
- if (!willAcceptDrop(info) || isAllAppsWidget(source, info)) {
- isVisible = false;
- }
- if (useUninstallLabel) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- UserManager userManager = (UserManager)
- getContext().getSystemService(Context.USER_SERVICE);
- Bundle restrictions = userManager.getUserRestrictions();
- if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
- || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
- isVisible = false;
- }
- }
- }
-
- if (useUninstallLabel) {
- setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
- } else if (useDeleteLabel) {
- setCompoundDrawablesRelativeWithIntrinsicBounds(mRemoveDrawable, null, null, null);
- } else {
- isVisible = false;
- }
- mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
-
- mActive = isVisible;
- resetHoverColor();
- ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
- if (isVisible && getText().length() > 0) {
- setText(useUninstallLabel ? R.string.delete_target_uninstall_label
- : R.string.delete_target_label);
- }
+ protected boolean supportsDrop(DragSource source, Object info) {
+ return willAcceptDrop(source, info);
}
@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) {
- final DragLayer dragLayer = mLauncher.getDragLayer();
- final Rect from = new Rect();
- dragLayer.getViewRectRelativeToSelf(d.dragView, from);
-
- int width = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicWidth();
- int height = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicHeight();
- final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
- width, height);
- final float scale = (float) to.width() / from.width();
-
- mSearchDropTargetBar.deferOnDragEnd();
- deferCompleteDropIfUninstalling(d);
-
- Runnable onAnimationEndRunnable = new Runnable() {
- @Override
- public void run() {
- completeDrop(d);
- mSearchDropTargetBar.onDragEnd();
- mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
- }
- };
- 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 deferCompleteDropIfUninstalling(DragObject d) {
- mWaitingForUninstall = false;
- if (isUninstallFromWorkspace(d)) {
- if (d.dragSource instanceof Folder) {
- ((Folder) d.dragSource).deferCompleteDropAfterUninstallActivity();
- } else if (d.dragSource instanceof Workspace) {
- ((Workspace) d.dragSource).deferCompleteDropAfterUninstallActivity();
- }
- mWaitingForUninstall = true;
- }
- }
-
- private boolean isUninstallFromWorkspace(DragObject d) {
- if (LauncherAppState.isDisableAllApps() && isDragSourceWorkspaceOrFolder(d)
- && (d.dragInfo instanceof ShortcutInfo)) {
- ShortcutInfo shortcut = (ShortcutInfo) d.dragInfo;
- // Only allow manifest shortcuts to initiate an un-install.
- return !InstallShortcutReceiver.isValidShortcutLaunchIntent(shortcut.intent);
- }
- return false;
- }
-
- private void completeDrop(DragObject d) {
+ @Thunk void completeDrop(DragObject d) {
ItemInfo item = (ItemInfo) d.dragInfo;
- boolean wasWaitingForUninstall = mWaitingForUninstall;
- mWaitingForUninstall = false;
- if (isAllAppsApplication(d.dragSource, item)) {
- uninstallApp(mLauncher, (AppInfo) item);
- } else if (isUninstallFromWorkspace(d)) {
- ShortcutInfo shortcut = (ShortcutInfo) item;
- if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
- final ComponentName componentName = shortcut.intent.getComponent();
- final DragSource dragSource = d.dragSource;
- final UserHandleCompat user = shortcut.user;
- mWaitingForUninstall = mLauncher.startApplicationUninstallActivity(
- componentName, shortcut.flags, user);
- if (mWaitingForUninstall) {
- final Runnable checkIfUninstallWasSuccess = new Runnable() {
- @Override
- public void run() {
- mWaitingForUninstall = false;
- String packageName = componentName.getPackageName();
- boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
- getContext(), packageName, user);
- if (dragSource instanceof Folder) {
- ((Folder) dragSource).
- onUninstallActivityReturned(uninstallSuccessful);
- } else if (dragSource instanceof Workspace) {
- ((Workspace) dragSource).
- onUninstallActivityReturned(uninstallSuccessful);
- }
- }
- };
- mLauncher.addOnResumeCallback(checkIfUninstallWasSuccess);
- }
- }
- } else if (isDragSourceWorkspaceOrFolder(d)) {
+ if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
removeWorkspaceOrFolderItem(mLauncher, item, null);
}
- if (wasWaitingForUninstall && !mWaitingForUninstall) {
- if (d.dragSource instanceof Folder) {
- ((Folder) d.dragSource).onUninstallActivityReturned(false);
- } else if (d.dragSource instanceof Workspace) {
- ((Workspace) d.dragSource).onUninstallActivityReturned(false);
- }
- }
- }
-
- public static void uninstallApp(Launcher launcher, AppInfo info) {
- launcher.startApplicationUninstallActivity(info.componentName, info.flags, info.user);
}
/**
@@ -365,7 +105,7 @@ public class DeleteDropTarget extends ButtonDropTarget {
appWidgetHost.deleteAppWidgetId(widget.appWidgetId);
return null;
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
return false;
@@ -378,18 +118,14 @@ public class DeleteDropTarget extends ButtonDropTarget {
return true;
}
- 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) {
- int width = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicWidth();
- int height = mCurrentDrawable == null ? 0 : mCurrentDrawable.getIntrinsicHeight();
+ int width = mDrawable.getIntrinsicWidth();
+ int height = mDrawable.getIntrinsicHeight();
final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
width, height);
final Rect from = new Rect();
@@ -502,13 +238,14 @@ public class DeleteDropTarget extends ButtonDropTarget {
}
public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
- final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView;
+ final boolean isWidgets = d.dragSource instanceof WidgetsContainerView;
+ final boolean isAllapps = d.dragSource instanceof AppsContainerView;
// 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) {
+ if (isWidgets || isAllapps) {
resetHoverColor();
}
@@ -551,14 +288,13 @@ public class DeleteDropTarget extends ButtonDropTarget {
updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
duration, config);
}
- deferCompleteDropIfUninstalling(d);
Runnable onAnimationEndRunnable = new Runnable() {
@Override
public void run() {
// 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) {
+ if (!isWidgets || !isAllapps) {
mLauncher.exitSpringLoadedDragMode();
completeDrop(d);
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 34e1f3c5f..22fb6a049 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -38,6 +38,8 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import com.android.launcher3.util.Thunk;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -80,7 +82,8 @@ public class DeviceProfile {
boolean isLandscape;
boolean isTablet;
boolean isLargeTablet;
- boolean isLayoutRtl;
+ public boolean isLayoutRtl;
+
boolean transposeLayoutWithOrientation;
int desiredWorkspaceLeftRightMarginPx;
@@ -103,8 +106,8 @@ public class DeviceProfile {
public int cellWidthPx;
public int cellHeightPx;
- int iconSizePx;
- int iconTextSizePx;
+ public int iconSizePx;
+ public int iconTextSizePx;
int iconDrawablePaddingPx;
int allAppsIconSizePx;
int allAppsIconTextSizePx;
@@ -122,6 +125,7 @@ public class DeviceProfile {
int hotseatAllAppsRank;
int allAppsNumRows;
int allAppsNumCols;
+ int appsViewNumCols;
int searchBarSpaceWidthPx;
int searchBarSpaceHeightPx;
int pageIndicatorHeightPx;
@@ -137,7 +141,7 @@ public class DeviceProfile {
DeviceProfile(String n, float w, float h, float r, float c,
float is, float its, float hs, float his, int dlId) {
// Ensure that we have an odd number of hotseat items (since we need to place all apps)
- if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
+ if (hs % 2 == 0) {
throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
}
@@ -363,7 +367,7 @@ public class DeviceProfile {
}
}
- private void updateIconSize(float scale, int drawablePadding, Resources resources,
+ private void updateIconSize(float scale, int drawablePadding, Resources res,
DisplayMetrics dm) {
iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
@@ -372,9 +376,9 @@ public class DeviceProfile {
// Search Bar
searchBarSpaceWidthPx = Math.min(widthPx,
- resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
searchBarSpaceHeightPx = getSearchBarTopOffset()
- + resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
// Calculate the actual text height
Paint textPaint = new Paint();
@@ -382,7 +386,7 @@ public class DeviceProfile {
FontMetrics fm = textPaint.getFontMetrics();
cellWidthPx = iconSizePx;
cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
- final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
+ final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
// Hotseat
@@ -400,11 +404,11 @@ public class DeviceProfile {
allAppsCellWidthPx = allAppsIconSizePx;
allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
int maxLongEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
+ res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
int maxShortEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
+ res.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
int minEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
+ res.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
@@ -415,10 +419,25 @@ public class DeviceProfile {
allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
(allAppsCellHeightPx + allAppsCellPaddingPx);
allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
- allAppsNumCols = (availableWidthPx) /
- (allAppsCellWidthPx + allAppsCellPaddingPx);
+ allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx);
allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
}
+
+ int appsContainerViewWidthPx = res.getDimensionPixelSize(R.dimen.apps_container_width);
+ updateAppsViewNumCols(res, appsContainerViewWidthPx);
+ }
+
+ public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
+ int appsViewLeftMarginPx =
+ res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+ int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
+ int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
+ (allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
+ if (numCols != appsViewNumCols) {
+ appsViewNumCols = numCols;
+ return true;
+ }
+ return false;
}
void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
@@ -440,7 +459,7 @@ public class DeviceProfile {
updateAvailableDimensions(context);
}
- private float dist(PointF p0, PointF p1) {
+ @Thunk float dist(PointF p0, PointF p1) {
return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
(p1.y-p0.y)*(p1.y-p0.y));
}
@@ -691,6 +710,10 @@ public class DeviceProfile {
return isLargeTablet;
}
+ /**
+ * When {@code true}, hotseat is on the bottom row when in landscape mode.
+ * If {@code false}, hotseat is on the right column when in landscape mode.
+ */
boolean isVerticalBarLayout() {
return isLandscape && transposeLayoutWithOrientation;
}
@@ -788,64 +811,6 @@ public class DeviceProfile {
}
}
- // Layout AllApps
- AppsCustomizeTabHost host = (AppsCustomizeTabHost)
- launcher.findViewById(R.id.apps_customize_pane);
- if (host != null) {
- // Center the all apps page indicator
- int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
- (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
- pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
- if (pageIndicator != null) {
- LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
- lllp.width = LayoutParams.WRAP_CONTENT;
- lllp.height = pageIndicatorHeight;
- pageIndicator.setLayoutParams(lllp);
- }
-
- AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
- host.findViewById(R.id.apps_customize_pane_content);
-
- FrameLayout fakePageContainer = (FrameLayout)
- host.findViewById(R.id.fake_page_container);
- FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
-
- padding = new Rect();
- if (pagedView != null) {
- // Constrain the dimensions of all apps so that it does not span the full width
- int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
- (2 * (allAppsNumCols + 1));
- int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
- (2 * (allAppsNumRows + 1));
- paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
- paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
- int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
- int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
- // Only adjust the side paddings on landscape phones, or tablets
- if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
- padding.left = padding.right = gridPaddingLR;
- }
-
- // The icons are centered, so we can't just offset by the page indicator height
- // because the empty space will actually be pageIndicatorHeight + paddingTB
- padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
-
- pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
- fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
-
- // Horizontal padding for the whole paged view
- int pagedFixedViewPadding =
- res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
-
- padding.left += pagedFixedViewPadding;
- padding.right += pagedFixedViewPadding;
-
- pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
- fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
-
- }
- }
-
// Layout the Overview Mode
ViewGroup overviewMode = launcher.getOverviewPanel();
if (overviewMode != null) {
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 480dce999..b24608cb1 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -34,6 +34,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.inputmethod.InputMethodManager;
+import com.android.launcher3.util.Thunk;
+
import java.util.ArrayList;
import java.util.HashSet;
@@ -49,8 +51,8 @@ public class DragController {
/** 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 = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+ public static final int SCROLL_DELAY = 500;
+ public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
@@ -63,7 +65,7 @@ public class DragController {
private static final float MAX_FLING_DEGREES = 35f;
- private Launcher mLauncher;
+ @Thunk Launcher mLauncher;
private Handler mHandler;
// temporaries to avoid gc thrash
@@ -73,6 +75,9 @@ public class DragController {
/** Whether or not we're dragging. */
private boolean mDragging;
+ /** Whether or not this is an accessible drag operation */
+ private boolean mIsAccessibleDrag;
+
/** X coordinate of the down event. */
private int mMotionDownX;
@@ -99,17 +104,17 @@ public class DragController {
private View mMoveTarget;
- private DragScroller mDragScroller;
- private int mScrollState = SCROLL_OUTSIDE_ZONE;
+ @Thunk DragScroller mDragScroller;
+ @Thunk 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;
+ @Thunk int mLastTouch[] = new int[2];
+ @Thunk long mLastTouchUpTime = -1;
+ @Thunk int mDistanceSinceScroll = 0;
private int mTmpPoint[] = new int[2];
private Rect mDragLayerRect = new Rect();
@@ -120,7 +125,7 @@ public class DragController {
/**
* Interface to receive notifications when a drag starts or stops
*/
- interface DragListener {
+ public interface DragListener {
/**
* A drag has begun
*
@@ -182,7 +187,7 @@ public class DragController {
(int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
- null, initialDragViewScale);
+ null, initialDragViewScale, false);
if (dragAction == DRAG_ACTION_MOVE) {
v.setVisibility(View.GONE);
@@ -202,10 +207,11 @@ public class DragController {
* {@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
+ * @param accessible whether this drag should occur in accessibility mode
*/
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
- float initialDragViewScale) {
+ float initialDragViewScale, boolean accessible) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
@@ -228,12 +234,21 @@ public class DragController {
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
mDragging = true;
+ mIsAccessibleDrag = accessible;
mDragObject = new DropTarget.DragObject();
mDragObject.dragComplete = false;
- mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
- mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+ if (mIsAccessibleDrag) {
+ // For an accessible drag, we assume the view is being dragged from the center.
+ mDragObject.xOffset = b.getWidth() / 2;
+ mDragObject.yOffset = b.getHeight() / 2;
+ mDragObject.accessibleDrag = true;
+ } else {
+ mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
+ mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+ }
+
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
@@ -349,6 +364,7 @@ public class DragController {
private void endDrag() {
if (mDragging) {
mDragging = false;
+ mIsAccessibleDrag = false;
clearScrollRunnable();
boolean isDeferred = false;
if (mDragObject.dragView != null) {
@@ -361,7 +377,7 @@ public class DragController {
// Only end the drag if we are not deferred
if (!isDeferred) {
- for (DragListener listener : mListeners) {
+ for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragEnd();
}
}
@@ -378,13 +394,13 @@ public class DragController {
if (mDragObject.deferDragViewCleanupPostAnimation) {
// If we skipped calling onDragEnd() before, do it now
- for (DragListener listener : mListeners) {
+ for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragEnd();
}
}
}
- void onDeferredEndFling(DropTarget.DragObject d) {
+ public void onDeferredEndFling(DropTarget.DragObject d) {
d.dragSource.onFlingToDeleteCompleted();
}
@@ -421,6 +437,10 @@ public class DragController {
+ mDragging);
}
+ if (mIsAccessibleDrag) {
+ return false;
+ }
+
// Update the velocity tracker
acquireVelocityTrackerAndAddMovement(ev);
@@ -442,7 +462,8 @@ public class DragController {
mLastTouchUpTime = System.currentTimeMillis();
if (mDragging) {
PointF vec = isFlingingToDelete(mDragObject.dragSource);
- if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
+ if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource,
+ mDragObject.dragInfo)) {
vec = null;
}
if (vec != null) {
@@ -525,7 +546,7 @@ public class DragController {
mLastDropTarget = dropTarget;
}
- private void checkScrollState(int x, int y) {
+ @Thunk void checkScrollState(int x, int y) {
final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
final DragLayer dragLayer = mLauncher.getDragLayer();
@@ -560,7 +581,7 @@ public class DragController {
* Call this from a drag source view.
*/
public boolean onTouchEvent(MotionEvent ev) {
- if (!mDragging) {
+ if (!mDragging || mIsAccessibleDrag) {
return false;
}
@@ -596,7 +617,7 @@ public class DragController {
if (mDragging) {
PointF vec = isFlingingToDelete(mDragObject.dragSource);
- if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
+ if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource, mDragObject.dragInfo)) {
vec = null;
}
if (vec != null) {
@@ -617,6 +638,34 @@ public class DragController {
}
/**
+ * Since accessible drag and drop won't cause the same sequence of touch events, we manually
+ * inject the appropriate state.
+ */
+ public void prepareAccessibleDrag(int x, int y) {
+ mMotionDownX = x;
+ mMotionDownY = y;
+ mLastDropTarget = null;
+ }
+
+ /**
+ * As above, since accessible drag and drop won't cause the same sequence of touch events,
+ * we manually ensure appropriate drag and drop events get emulated for accessible drag.
+ */
+ public void completeAccessibleDrag(int[] location) {
+ final int[] coordinates = mCoordinatesTemp;
+
+ // We make sure that we prime the target for drop.
+ DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
+ mDragObject.x = coordinates[0];
+ mDragObject.y = coordinates[1];
+ checkTouchMove(dropTarget);
+
+ // Perform the drop
+ drop(location[0], location[1]);
+ endDrag();
+ }
+
+ /**
* 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.
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index a352b7914..ab2e094cb 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -39,6 +39,7 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -46,7 +47,7 @@ import java.util.ArrayList;
* A ViewGroup that coordinates dragging across its descendants
*/
public class DragLayer extends InsettableFrameLayout {
- private DragController mDragController;
+ @Thunk DragController mDragController;
private int[] mTmpXY = new int[2];
private int mXDown, mYDown;
@@ -61,9 +62,9 @@ public class DragLayer extends InsettableFrameLayout {
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;
+ @Thunk DragView mDropView = null;
+ @Thunk int mAnchorViewInitialScrollX = 0;
+ @Thunk View mAnchorView = null;
private boolean mHoverPointClosesFolder = false;
private Rect mHitRect = new Rect();
@@ -779,7 +780,7 @@ public class DragLayer extends InsettableFrameLayout {
return mDropView;
}
- private void fadeOutDragView() {
+ @Thunk void fadeOutDragView() {
mFadeOutAnim = new ValueAnimator();
mFadeOutAnim.setDuration(150);
mFadeOutAnim.setFloatValues(0f, 1f);
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index 7369eeac2..2a1346ef5 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -22,9 +22,9 @@ import com.android.launcher3.DropTarget.DragObject;
/**
* Interface defining an object that can originate a drag.
- *
*/
public interface DragSource {
+
/**
* @return whether items dragged from this source supports
*/
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index ea34e46f9..b1a6266cc 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -29,8 +29,10 @@ import android.graphics.Rect;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
+import com.android.launcher3.util.Thunk;
+
public class DragView extends View {
- private static float sDragAlpha = 1f;
+ @Thunk static float sDragAlpha = 1f;
private Bitmap mBitmap;
private Bitmap mCrossFadeBitmap;
@@ -42,11 +44,11 @@ public class DragView extends View {
private Rect mDragRegion = null;
private DragLayer mDragLayer = null;
private boolean mHasDrawn = false;
- private float mCrossFadeProgress = 0f;
+ @Thunk float mCrossFadeProgress = 0f;
ValueAnimator mAnim;
- private float mOffsetX = 0.0f;
- private float mOffsetY = 0.0f;
+ @Thunk float mOffsetX = 0.0f;
+ @Thunk float mOffsetY = 0.0f;
private float mInitialScale = 1f;
// The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
// size. This is ignored for non-icons.
@@ -70,8 +72,6 @@ public class DragView extends View {
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;
@@ -87,8 +87,8 @@ public class DragView extends View {
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);
+ final int deltaX = (int) (-mOffsetX);
+ final int deltaY = (int) (-mOffsetY);
mOffsetX += deltaX;
mOffsetY += deltaY;
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 64f0ac867..c5cca3b28 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -54,6 +54,9 @@ public interface DropTarget {
/** Where the drag originated */
public DragSource dragSource = null;
+ /** The object is part of an accessible drag operation */
+ public boolean accessibleDrag;
+
/** Post drag animation runnable */
public Runnable postAnimationRunnable = null;
@@ -65,6 +68,29 @@ public interface DropTarget {
public DragObject() {
}
+
+ /**
+ * 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.
+ */
+ public final float[] getVisualCenter(float[] recycle) {
+ final float res[] = (recycle == null) ? new float[2] : 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;
+ }
}
public static class DragEnforcer implements DragController.DragListener {
@@ -113,7 +139,7 @@ public interface DropTarget {
/**
* 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
@@ -143,7 +169,7 @@ public interface DropTarget {
/**
* 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
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index aa08148d2..24da97fc6 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -56,23 +56,22 @@ public class DynamicGrid {
DisplayMetrics dm = resources.getDisplayMetrics();
ArrayList<DeviceProfile> deviceProfiles =
new ArrayList<DeviceProfile>();
- boolean hasAA = !LauncherAppState.isDisableAllApps();
DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
// Our phone profiles include the bar sizes in each orientation
deviceProfiles.add(new DeviceProfile("Super Short Stubby",
- 255, 300, 2, 3, 48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4));
+ 255, 300, 2, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Shorter Stubby",
- 255, 400, 3, 3, 48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4));
+ 255, 400, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Short Stubby",
- 275, 420, 3, 4, 48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4));
+ 275, 420, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Stubby",
- 255, 450, 3, 4, 48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4));
+ 255, 450, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Nexus S",
- 296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4));
+ 296, 491.33f, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Nexus 4",
- 335, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
+ 335, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Nexus 5",
- 359, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
+ 359, 567, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
deviceProfiles.add(new DeviceProfile("Large Phone",
406, 694, 5, 5, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
// The tablet profile is odd in that the landscape orientation
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index ff02bbbc3..28e923e67 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -32,7 +32,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.SparseArray;
-class FastBitmapDrawable extends Drawable {
+public class FastBitmapDrawable extends Drawable {
static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
@@ -72,7 +72,7 @@ class FastBitmapDrawable extends Drawable {
private boolean mPressed = false;
private ObjectAnimator mPressedAnimator;
- FastBitmapDrawable(Bitmap b) {
+ public FastBitmapDrawable(Bitmap b) {
mAlpha = 255;
mBitmap = b;
setBounds(0, 0, b.getWidth(), b.getHeight());
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 095c5631d..a51ddd4b8 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -24,6 +24,8 @@ import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
+import com.android.launcher3.util.Thunk;
+
/*
* This is a helper class that listens to updates from the corresponding animation.
* For the first two frames, it adjusts the current play time of the animation to
@@ -41,7 +43,7 @@ public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
private boolean mAdjustedSecondFrameTime;
private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
- private static long sGlobalFrameCounter;
+ @Thunk static long sGlobalFrameCounter;
private static boolean sVisible;
public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index e60704718..c77d41657 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -16,658 +16,439 @@
package com.android.launcher3;
-import android.content.res.Configuration;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ScrollView;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
+import com.android.launcher3.util.FocusLogic;
+import com.android.launcher3.util.Thunk;
/**
* A keyboard listener we set on all the workspace icons.
*/
class IconKeyEventListener implements View.OnKeyListener {
+ @Override
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 {
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
- final Configuration configuration = v.getResources().getConfiguration();
- return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
+ return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event);
}
}
public class FocusHelper {
- /**
- * 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 CellLayout) {
- // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
- page = ((CellLayout) page).getShortcutsAndWidgets();
- }
- return page;
- }
+ private static final String TAG = "FocusHelper";
+ private static final boolean DEBUG = false;
/**
- * Handles key events in a PageViewCellLayout containing PagedViewIcons.
+ * Handles key events in paged folder.
*/
- static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
- ViewGroup parentLayout;
- ViewGroup itemContainer;
- int countX;
- int countY;
- if (v.getParent() instanceof ShortcutAndWidgetContainer) {
- itemContainer = (ViewGroup) v.getParent();
- parentLayout = (ViewGroup) itemContainer.getParent();
- countX = ((CellLayout) parentLayout).getCountX();
- countY = ((CellLayout) parentLayout).getCountY();
- } else {
- itemContainer = parentLayout = (ViewGroup) v.getParent();
- countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
- countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
+ public static class PagedFolderKeyEventListener implements View.OnKeyListener {
+
+ private final Folder mFolder;
+
+ public PagedFolderKeyEventListener(Folder folder) {
+ mFolder = folder;
}
- // Note we have an extra parent because of the
- // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
- final PagedView container = (PagedView) parentLayout.getParent();
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- } 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
- }
- }
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP) {
+ return consume;
+ }
+ if (DEBUG) {
+ Log.v(TAG, String.format("Handle ALL Folders keyevent=[%s].",
+ KeyEvent.keyCodeToString(keyCode)));
+ }
+
+
+ if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
+ if (LauncherAppState.isDogfoodBuild()) {
+ throw new IllegalStateException("Parent of the focused item is not supported.");
+ } else {
+ return false;
}
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- } 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- }
- }
+ }
+
+ // Initialize variables.
+ final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
+ final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
+ final int countX = cellLayout.getCountX();
+ final int countY = cellLayout.getCountY();
+
+ final int iconIndex = itemContainer.indexOfChild(v);
+ final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
+
+ final int pageIndex = pagedView.indexOfChild(cellLayout);
+ final int pageCount = pagedView.getPageCount();
+
+ int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
+ // Process focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+ if (newIconIndex == FocusLogic.NOOP) {
+ handleNoopKey(keyCode, v);
+ return consume;
+ }
+ ShortcutAndWidgetContainer newParent = null;
+ View child = null;
+
+ switch (newIconIndex) {
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
+ newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
+ if (newParent != null) {
+ int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
+ pagedView.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(
+ ((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
+ ^ newParent.invertLayoutHorizontally()) ? 0 : countX - 1, row);
}
- }
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
+ break;
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
+ if (newParent != null) {
+ pagedView.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(0, 0);
}
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (handleKeyEvent) {
- // Select the closest icon in the next row, otherwise do nothing
- if (y < (countY - 1)) {
- int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
- int newIconY = newiconIndex / countX;
- if (newIconY != y) {
- itemContainer.getChildAt(newiconIndex).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
+ break;
+ case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+ newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
+ if (newParent != null) {
+ pagedView.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(countX - 1, countY - 1);
}
- }
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- } else {
- itemContainer.getChildAt(0).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
+ break;
+ case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+ newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
+ if (newParent != null) {
+ pagedView.snapToPage(pageIndex + 1);
+ child = newParent.getChildAt(0, 0);
}
- }
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- } else {
- itemContainer.getChildAt(itemCount - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
+ break;
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
+ newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
+ if (newParent != null) {
+ pagedView.snapToPage(pageIndex + 1);
+ child = FocusLogic.getAdjacentChildInNextPage(newParent, v, newIconIndex);
}
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_MOVE_HOME:
- if (handleKeyEvent) {
- // Select the first icon on this page
- itemContainer.getChildAt(0).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_MOVE_END:
- if (handleKeyEvent) {
- // Select the last icon on this page
- itemContainer.getChildAt(itemCount - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- wasHandled = true;
- break;
- default: break;
+ break;
+ case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+ child = cellLayout.getChildAt(0, 0);
+ break;
+ case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+ child = pagedView.getLastItem();
+ break;
+ default: // Go to some item on the current page.
+ child = itemContainer.getChildAt(newIconIndex);
+ break;
+ }
+ if (child != null) {
+ child.requestFocus();
+ playSoundEffect(keyCode, v);
+ } else {
+ handleNoopKey(keyCode, v);
+ }
+ return consume;
}
- return wasHandled;
- }
- /**
- * Handles key events in the workspace hotseat (bottom of the screen).
- */
- static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
- ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout layout = (CellLayout) parent.getParent();
-
- // 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) {
- ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- int myIndex = views.indexOf(v);
- // Select the previous button, otherwise do nothing
- if (myIndex > 0) {
- views.get(myIndex - 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (handleKeyEvent) {
- ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- int myIndex = views.indexOf(v);
- // Select the next button, otherwise do nothing
- if (myIndex < views.size() - 1) {
- views.get(myIndex + 1).requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (handleKeyEvent) {
- final Workspace workspace = (Workspace)
- v.getRootView().findViewById(R.id.workspace);
- if (workspace != null) {
- int pageIndex = workspace.getCurrentPage();
- CellLayout topLayout = (CellLayout) workspace.getChildAt(pageIndex);
- ShortcutAndWidgetContainer children = topLayout.getShortcutsAndWidgets();
- final View newIcon = getIconInDirection(layout, children, -1, 1);
- // Select the first bubble text view in the current page of the workspace
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- } else {
- workspace.requestFocus();
- }
- }
- }
- wasHandled = true;
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- // Do nothing
- wasHandled = true;
- break;
- default: break;
+ public void handleNoopKey(int keyCode, View v) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ mFolder.mFolderName.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
}
- return wasHandled;
}
/**
- * Private helper method to get the CellLayoutChildren given a CellLayout index.
+ * Handles key events in the workspace hot seat (bottom of the screen).
+ * <p>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.
*/
- private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
- ViewGroup container, int i) {
- CellLayout parent = (CellLayout) container.getChildAt(i);
- return parent.getShortcutsAndWidgets();
- }
+ static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
+ return consume;
+ }
- /**
- * 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<View> 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<View> views = new ArrayList<View>();
- for (int j = 0; j < count; ++j) {
- views.add(parent.getChildAt(j));
+ DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "Handle HOTSEAT BUTTONS keyevent=[%s] on hotseat buttons, isVertical=%s",
+ KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
}
- Collections.sort(views, new Comparator<View>() {
- @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<View> 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;
+
+ // Initialize the variables.
+ final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
+ final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
+ Hotseat hotseat = (Hotseat) hotseatLayout.getParent();
+
+ Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+ int pageIndex = workspace.getNextPage();
+ int pageCount = workspace.getChildCount();
+ int countX = -1;
+ int countY = -1;
+ int iconIndex = hotseatParent.indexOfChild(v);
+ int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
+ .getChildAt(iconIndex).getLayoutParams()).cellX;
+
+ final CellLayout iconLayout = (CellLayout) workspace.getChildAt(pageIndex);
+ if (iconLayout == null) {
+ // This check is to guard against cases where key strokes rushes in when workspace
+ // child creation/deletion is still in flux. (e.g., during drop or fling
+ // animation.)
+ return consume;
}
- return null;
- }
- private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
- int delta) {
- final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
- return findIndexOfIcon(views, i, delta);
- }
- private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
- int delta) {
- final ArrayList<View> 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<View> 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;
- }
+ final ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+ ViewGroup parent = null;
+ int[][] matrix = null;
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
+ !profile.isVerticalBarLayout()) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
+ true /* hotseat horizontal */, hotseat.getAllAppsButtonRank(),
+ iconRank == hotseat.getAllAppsButtonRank() /* include all apps icon */);
+ iconIndex += iconParent.getChildCount();
+ countX = iconLayout.getCountX();
+ countY = iconLayout.getCountY() + hotseatLayout.getCountY();
+ parent = iconParent;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
+ profile.isVerticalBarLayout()) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
+ false /* hotseat horizontal */, hotseat.getAllAppsButtonRank(),
+ iconRank == hotseat.getAllAppsButtonRank() /* include all apps icon */);
+ iconIndex += iconParent.getChildCount();
+ countX = iconLayout.getCountX() + hotseatLayout.getCountX();
+ countY = iconLayout.getCountY();
+ parent = iconParent;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
+ profile.isVerticalBarLayout()) {
+ keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
+ }else {
+ // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
+ // matrix extended with hotseat.
+ matrix = FocusLogic.createSparseMatrix(hotseatLayout);
+ countX = hotseatLayout.getCountX();
+ countY = hotseatLayout.getCountY();
+ parent = hotseatParent;
+ }
+
+ // Process the focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+
+ View newIcon = null;
+ if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ newIcon = parent.getChildAt(0);
+ // TODO(hyunyoungs): handle cases where the child is not an icon but
+ // a folder or a widget.
+ workspace.snapToPage(pageIndex + 1);
+ }
+ if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
+ newIconIndex -= iconParent.getChildCount();
+ }
+ if (parent != null) {
+ if (newIcon == null && newIconIndex >=0) {
+ newIcon = parent.getChildAt(newIconIndex);
}
- if (closestIndex > -1) {
- return views.get(closestIndex);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ playSoundEffect(keyCode, v);
}
}
- return null;
+ return consume;
}
/**
- * Handles key events in a Workspace containing.
+ * Handles key events in a workspace containing icons.
*/
static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
+ boolean consume = FocusLogic.shouldConsume(keyCode);
+ if (e.getAction() == KeyEvent.ACTION_UP || !consume) {
+ return consume;
+ }
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile profile = app.getDynamicGrid().getDeviceProfile();
+
+ if (DEBUG) {
+ Log.v(TAG, String.format("Handle WORKSPACE ICONS keyevent=[%s] isVerticalBar=%s",
+ KeyEvent.keyCodeToString(keyCode), profile.isVerticalBarLayout()));
+ }
+
+ // Initialize the variables.
ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout layout = (CellLayout) parent.getParent();
- final Workspace workspace = (Workspace) layout.getParent();
+ CellLayout iconLayout = (CellLayout) parent.getParent();
+ final Workspace workspace = (Workspace) iconLayout.getParent();
final ViewGroup launcher = (ViewGroup) workspace.getParent();
final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
- final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
- int pageIndex = workspace.indexOfChild(layout);
- int pageCount = workspace.getChildCount();
+ final Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- } 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);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
+ final int iconIndex = parent.indexOfChild(v);
+ final int pageIndex = workspace.indexOfChild(iconLayout);
+ final int pageCount = workspace.getChildCount();
+ int countX = iconLayout.getCountX();
+ int countY = iconLayout.getCountY();
+
+ CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
+ ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
+ int[][] matrix;
+
+ // KEYCODE_DPAD_DOWN in portrait (KEYCODE_DPAD_RIGHT in landscape) is the only key allowed
+ // to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
+ // with the hotseat.
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, true /* horizontal */,
+ hotseat.getAllAppsButtonRank(), false /* all apps icon is ignored */);
+ countY = countY + 1;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
+ profile.isVerticalBarLayout()) {
+ matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, false /* horizontal */,
+ hotseat.getAllAppsButtonRank(), false /* all apps icon is ignored */);
+ countX = countX + 1;
+ } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
+ workspace.removeWorkspaceItem(v);
+ return consume;
+ } else {
+ matrix = FocusLogic.createSparseMatrix(iconLayout);
+ }
+
+ // Process the focus.
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
+ iconIndex, pageIndex, pageCount);
+ View newIcon = null;
+ switch (newIconIndex) {
+ case FocusLogic.NOOP:
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+ newIcon = tabs;
}
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- } 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);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- }
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
+ int newPageIndex = pageIndex - 1;
+ if (newIconIndex == FocusLogic.NEXT_PAGE_RIGHT_COLUMN) {
+ newPageIndex = 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();
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
+ int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
+ parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
+ workspace.snapToPage(newPageIndex);
+ if (parent != null) {
+ workspace.snapToPage(newPageIndex);
+ iconLayout = (CellLayout) parent.getParent();
+ matrix = FocusLogic.createSparseMatrix(iconLayout,
+ iconLayout.getCountX(), row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+ FocusLogic.PIVOT, newPageIndex, pageCount);
+ newIcon = parent.getChildAt(newIconIndex);
}
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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- wasHandled = true;
- } else if (hotseat != null) {
- hotseat.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(0);
+ workspace.snapToPage(pageIndex - 1);
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);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- } else {
- View newIcon = getIconInDirection(layout, parent, -1, 1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- }
- wasHandled = true;
+ case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(parent.getChildCount() - 1);
+ workspace.snapToPage(pageIndex - 1);
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);
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- } else {
- View newIcon = getIconInDirection(layout, parent,
- parent.getChildCount(), -1);
- if (newIcon != null) {
- newIcon.requestFocus();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- }
- wasHandled = true;
+ case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ newIcon = parent.getChildAt(0);
+ workspace.snapToPage(pageIndex + 1);
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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
+ newPageIndex = pageIndex + 1;
+ if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
+ newPageIndex = pageIndex - 1;
+ }
+ workspace.snapToPage(newPageIndex);
+ row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
+ parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
+ if (parent != null) {
+ workspace.snapToPage(newPageIndex);
+ iconLayout = (CellLayout) parent.getParent();
+ matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+ FocusLogic.PIVOT, newPageIndex, pageCount);
+ newIcon = parent.getChildAt(newIconIndex);
}
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
+ case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
+ newIcon = parent.getChildAt(0);
+ break;
+ case FocusLogic.CURRENT_PAGE_LAST_ITEM:
+ newIcon = parent.getChildAt(parent.getChildCount() - 1);
+ break;
+ default:
+ // current page, some item.
+ if (0 <= newIconIndex && newIconIndex < parent.getChildCount()) {
+ newIcon = parent.getChildAt(newIconIndex);
+ } else if (parent.getChildCount() <= newIconIndex &&
+ newIconIndex < parent.getChildCount() + hotseatParent.getChildCount()) {
+ newIcon = hotseatParent.getChildAt(newIconIndex - parent.getChildCount());
}
- wasHandled = true;
break;
- default: break;
}
- return wasHandled;
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
+ return consume;
}
+ //
+ // Helper methods.
+ //
+
/**
- * Handles key events for items in a Folder.
+ * Private helper method to get the CellLayoutChildren given a CellLayout index.
*/
- static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
- ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout layout = (CellLayout) parent.getParent();
- final ScrollView scrollView = (ScrollView) layout.getParent();
- final Folder folder = (Folder) scrollView.getParent();
- View title = folder.mFolderName;
-
- final int action = e.getAction();
- final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
- boolean wasHandled = false;
+ private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
+ ViewGroup container, int i) {
+ CellLayout parent = (CellLayout) container.getChildAt(i);
+ return parent.getShortcutsAndWidgets();
+ }
+
+ /**
+ * Helper method to be used for playing sound effects.
+ */
+ @Thunk static void playSoundEffect(int keyCode, View v) {
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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- }
- wasHandled = true;
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
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();
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- 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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- wasHandled = true;
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
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();
- }
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- wasHandled = true;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ case KeyEvent.KEYCODE_MOVE_END:
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_PAGE_UP:
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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
- }
- }
- wasHandled = true;
+ v.playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
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();
- v.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN);
- }
- }
- wasHandled = true;
+ default:
break;
- default: break;
}
- return wasHandled;
}
}
diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java
index 7d4664abb..ecf93e4b3 100644
--- a/src/com/android/launcher3/FocusIndicatorView.java
+++ b/src/com/android/launcher3/FocusIndicatorView.java
@@ -23,7 +23,8 @@ import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
-import android.view.ViewParent;
+
+import com.android.launcher3.util.Thunk;
public class FocusIndicatorView extends View implements View.OnFocusChangeListener {
@@ -32,9 +33,6 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
private static final float MIN_VISIBLE_ALPHA = 0.2f;
private static final long ANIM_DURATION = 150;
- private static final int[] sTempPos = new int[2];
- private static final int[] sTempShift = new int[2];
-
private final int[] mIndicatorPos = new int[2];
private final int[] mTargetViewPos = new int[2];
@@ -80,7 +78,8 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
}
if (!mInitiated) {
- getLocationRelativeToParentPagedView(this, mIndicatorPos);
+ // The parent view should always the a parent of the target view.
+ computeLocationRelativeToParent(this, (View) getParent(), mIndicatorPos);
mInitiated = true;
}
@@ -93,7 +92,7 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
nextState.scaleX = v.getScaleX() * v.getWidth() / indicatorWidth;
nextState.scaleY = v.getScaleY() * v.getHeight() / indicatorHeight;
- getLocationRelativeToParentPagedView(v, mTargetViewPos);
+ computeLocationRelativeToParent(v, (View) getParent(), mTargetViewPos);
nextState.x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - nextState.scaleX) * indicatorWidth / 2;
nextState.y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - nextState.scaleY) * indicatorHeight / 2;
@@ -150,31 +149,36 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
}
/**
- * Gets the location of a view relative in the window, off-setting any shift due to
- * page view scroll
+ * Computes the location of a view relative to {@param parent}, off-setting
+ * any shift due to page view scroll.
+ * @param pos an array of two integers in which to hold the coordinates
*/
- private static void getLocationRelativeToParentPagedView(View v, int[] pos) {
- getPagedViewScrollShift(v, sTempShift);
- v.getLocationInWindow(sTempPos);
- pos[0] = sTempPos[0] + sTempShift[0];
- pos[1] = sTempPos[1] + sTempShift[1];
+ private static void computeLocationRelativeToParent(View v, View parent, int[] pos) {
+ pos[0] = pos[1] = 0;
+ computeLocationRelativeToParentHelper(v, parent, pos);
+
+ // If a view is scaled, its position will also shift accordingly. For optimization, only
+ // consider this for the last node.
+ pos[0] += (1 - v.getScaleX()) * v.getWidth() / 2;
+ pos[1] += (1 - v.getScaleY()) * v.getHeight() / 2;
}
- private static void getPagedViewScrollShift(View child, int[] shift) {
- ViewParent parent = child.getParent();
+ private static void computeLocationRelativeToParentHelper(View child,
+ View commonParent, int[] shift) {
+ View parent = (View) child.getParent();
+ shift[0] += child.getLeft();
+ shift[1] += child.getTop();
if (parent instanceof PagedView) {
- View parentView = (View) parent;
- child.getLocationInWindow(sTempPos);
- shift[0] = parentView.getPaddingLeft() - sTempPos[0];
- shift[1] = -(int) child.getTranslationY();
- } else if (parent instanceof View) {
- getPagedViewScrollShift((View) parent, shift);
- } else {
- shift[0] = shift[1] = 0;
+ PagedView page = (PagedView) parent;
+ shift[0] -= page.getScrollForPage(page.indexOfChild(child));
+ }
+
+ if (parent != commonParent) {
+ computeLocationRelativeToParentHelper(parent, commonParent, shift);
}
}
- private static final class ViewAnimState {
+ @Thunk static final class ViewAnimState {
float x, y, scaleX, scaleY;
}
}
diff --git a/src/com/android/launcher3/FocusOnlyTabWidget.java b/src/com/android/launcher3/FocusOnlyTabWidget.java
deleted file mode 100644
index 08fc311bc..000000000
--- a/src/com/android/launcher3/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.launcher3;
-
-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/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 66b656882..c35ce944f 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -21,12 +21,13 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.SystemClock;
-import android.support.v4.widget.AutoScrollHelper;
+import android.os.Build;
import android.text.InputType;
import android.text.Selection;
import android.text.Spannable;
@@ -39,124 +40,135 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
-import android.widget.ScrollView;
import android.widget.TextView;
+import com.android.launcher3.DragController.DragListener;
import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.UninstallDropTarget.UninstallSource;
+import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.util.Thunk;
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 {
+ View.OnFocusChangeListener, DragListener, UninstallSource {
private static final String TAG = "Launcher.Folder";
- protected DragController mDragController;
- protected Launcher mLauncher;
- protected FolderInfo mInfo;
+ /**
+ * We avoid measuring {@link #mContentWrapper} with a 0 width or height, as this
+ * results in CellLayout being measured as UNSPECIFIED, which it does not support.
+ */
+ private static final int MIN_CONTENT_DIMEN = 5;
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;
- private int mMaterialExpandDuration;
- private int mMaterialExpandStagger;
- protected CellLayout mContent;
- private ScrollView mScrollView;
- private final LayoutInflater mInflater;
- private final IconCache mIconCache;
- private int mState = STATE_NONE;
- private static final int REORDER_ANIMATION_DURATION = 230;
+ /**
+ * Time for which the scroll hint is shown before automatically changing page.
+ */
+ public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY;
+
+ /**
+ * Time in milliseconds for which an icon sticks to the target position
+ * in case of a sorted folder.
+ */
+ private static final int SORTED_STICKY_REORDER_DELAY = 1500;
+
+ /**
+ * Fraction of icon width which behave as scroll region.
+ */
+ private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f;
+
private static final int REORDER_DELAY = 250;
private static final int ON_EXIT_CLOSE_DELAY = 400;
+ private static final Rect sTempRect = new Rect();
+
+ private static String sDefaultFolderName;
+ private static String sHintText;
+
+ private final Alarm mReorderAlarm = new Alarm();
+ private final Alarm mOnExitAlarm = new Alarm();
+ private final Alarm mOnScrollHintAlarm = new Alarm();
+ @Thunk final Alarm mScrollPauseAlarm = new Alarm();
+
+ @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+
+ private final int mExpandDuration;
+ private final int mMaterialExpandDuration;
+ private final int mMaterialExpandStagger;
+
+ private final InputMethodManager mInputMethodManager;
+
+ protected final Launcher mLauncher;
+ protected DragController mDragController;
+ protected FolderInfo mInfo;
+
+ @Thunk FolderIcon mFolderIcon;
+
+ @Thunk FolderPagedView mContent;
+ @Thunk View mContentWrapper;
+ FolderEditText mFolderName;
+
+ private View mFooter;
+ private int mFooterHeight;
+
+ // Cell ranks used for drag and drop
+ @Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank;
+
+ @Thunk int mState = STATE_NONE;
private boolean mRearrangeOnClose = false;
- private FolderIcon mFolderIcon;
- private int mMaxCountX;
- private int mMaxCountY;
- private int mMaxNumItems;
- private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
boolean mItemsInvalidated = false;
private ShortcutInfo mCurrentDragInfo;
private View mCurrentDragView;
private boolean mIsExternalDrag;
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;
-
+ @Thunk float mFolderIconPivotX;
+ @Thunk float mFolderIconPivotY;
private boolean mIsEditingName = false;
- private InputMethodManager mInputMethodManager;
-
- private static String sDefaultFolderName;
- private static String sHintText;
-
- private FocusIndicatorView mFocusIndicatorHandler;
-
- // We avoid measuring the scroll view with a 0 width or height, as this
- // results in CellLayout being measured as UNSPECIFIED, which it does
- // not support.
- private static final int MIN_CONTENT_DIMEN = 5;
private boolean mDestroyed;
- private AutoScrollHelper mAutoScrollHelper;
-
- private Runnable mDeferredAction;
+ @Thunk Runnable mDeferredAction;
private boolean mDeferDropAfterUninstall;
private boolean mUninstallSuccessful;
+ // Folder scrolling
+ private int mScrollAreaOffset;
+
+ @Thunk int mScrollHintDir = DragController.SCROLL_NONE;
+ @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE;
+
/**
* Used to inflate the Workspace from XML.
*
* @param context The application's context.
- * @param attrs The attribtues set containing the Workspace's customization values.
+ * @param attrs The attributes set containing the Workspace's customization values.
*/
public Folder(Context context, AttributeSet attrs) {
super(context, attrs);
-
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
setAlwaysDrawnWithCacheEnabled(false);
- mInflater = LayoutInflater.from(context);
- mIconCache = app.getIconCache();
-
- Resources res = getResources();
- mMaxCountX = (int) grid.numColumns;
- // Allow scrolling folders when DISABLE_ALL_APPS is true.
- if (LauncherAppState.isDisableAllApps()) {
- mMaxCountY = mMaxNumItems = Integer.MAX_VALUE;
- } else {
- mMaxCountY = (int) grid.numRows;
- mMaxNumItems = mMaxCountX * mMaxCountY;
- }
-
mInputMethodManager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ Resources res = getResources();
mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger);
@@ -170,45 +182,38 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
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).
+ // reliable behavior when clicking the text field (since it will always gain focus on click).
setFocusableInTouchMode(true);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mScrollView = (ScrollView) findViewById(R.id.scroll_view);
- mContent = (CellLayout) findViewById(R.id.folder_content);
+ mContentWrapper = findViewById(R.id.folder_content_wrapper);
+ mContent = (FolderPagedView) findViewById(R.id.folder_content);
+ mContent.setFolder(this);
- mFocusIndicatorHandler = new FocusIndicatorView(getContext());
- mContent.addView(mFocusIndicatorHandler, 0);
- mFocusIndicatorHandler.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
- mFocusIndicatorHandler.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
-
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
- mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
- mContent.setGridSize(0, 0);
- mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
- mContent.setInvertIfRtl(true);
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);
- mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView);
+
+ mFooter = findViewById(R.id.folder_footer);
+ updateFooterHeight();
+ }
+
+ public void updateFooterHeight() {
+ // We find out how tall footer 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;
+ mFooter.measure(measureSpec, measureSpec);
+ mFooterHeight = mFooter.getMeasuredHeight();
}
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -246,14 +251,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return false;
}
- mLauncher.getWorkspace().beginDragShared(v, this);
+ mLauncher.getWorkspace().beginDragShared(v, new Point(), this, false);
mCurrentDragInfo = item;
- mEmptyCell[0] = item.cellX;
- mEmptyCell[1] = item.cellY;
+ mEmptyCellRank = item.rank;
mCurrentDragView = v;
- mContent.removeView(mCurrentDragView);
+ mContent.removeItem(mCurrentDragView);
mInfo.remove(mCurrentDragInfo);
mDragInProgress = true;
mItemAddedBackToSelfViaIcon = false;
@@ -307,10 +311,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return mFolderName;
}
- public CellLayout getContent() {
- return mContent;
- }
-
/**
* We need to handle touch events to prevent them from falling through to the workspace below.
*/
@@ -323,7 +323,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mDragController = dragController;
}
- void setFolderIcon(FolderIcon icon) {
+ public void setFolderIcon(FolderIcon icon) {
mFolderIcon = icon;
}
@@ -343,30 +343,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
void bind(FolderInfo info) {
mInfo = info;
ArrayList<ShortcutInfo> children = info.contents;
- ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
-
- final int totalChildren = children.size();
- setupContentForNumItems(totalChildren);
-
- // Arrange children in the grid based on the rank.
Collections.sort(children, Utilities.RANK_COMPARATOR);
- final int countX = mContent.getCountX();
-
- int visibleChildren = 0;
- for (int i = 0; i < children.size(); i++) {
- ShortcutInfo child = children.get(i);
- child.cellX = i % countX;
- child.cellY = i / countX;
-
- if (createAndAddShortcut(child) == null) {
- overflow.add(child);
- } else {
- visibleChildren++;
- }
- }
- // We rearrange the items in case there are any empty gaps
- setupContentForNumItems(visibleChildren);
+ ArrayList<ShortcutInfo> overflow = mContent.bindItems(children);
// 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
@@ -376,6 +355,14 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
LauncherModel.deleteItemFromDatabase(mLauncher, item);
}
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ if (lp == null) {
+ lp = new DragLayer.LayoutParams(0, 0);
+ lp.customPosition = true;
+ setLayoutParams(lp);
+ }
+ centerAboutIcon();
+
mItemsInvalidated = true;
updateTextViewFocus();
mInfo.addListener(this);
@@ -429,6 +416,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void animateOpen() {
if (!(getParent() instanceof DragLayer)) return;
+ mContent.completePendingPageChanges();
+ if (!(mDragInProgress && mContent.mIsSorted)) {
+ // Open on the first page.
+ mContent.snapToPageImmediately(0);
+ }
+
Animator openFolderAnim = null;
final Runnable onCompleteRunnable;
if (!Utilities.isLmpOrAbove()) {
@@ -473,14 +466,14 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
reveal.setDuration(mMaterialExpandDuration);
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
- mContent.setAlpha(0f);
- Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContent, "alpha", 0f, 1f);
+ mContentWrapper.setAlpha(0f);
+ Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContentWrapper, "alpha", 0f, 1f);
iconsAlpha.setDuration(mMaterialExpandDuration);
iconsAlpha.setStartDelay(mMaterialExpandStagger);
iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
- mFolderName.setAlpha(0f);
- Animator textAlpha = LauncherAnimUtils.ofFloat(mFolderName, "alpha", 0f, 1f);
+ mFooter.setAlpha(0f);
+ Animator textAlpha = LauncherAnimUtils.ofFloat(mFooter, "alpha", 0f, 1f);
textAlpha.setDuration(mMaterialExpandDuration);
textAlpha.setStartDelay(mMaterialExpandStagger);
textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
@@ -497,11 +490,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
openFolderAnim = anim;
- mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null);
onCompleteRunnable = new Runnable() {
@Override
public void run() {
- mContent.setLayerType(LAYER_TYPE_NONE, null);
+ mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
}
};
}
@@ -509,8 +502,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
@Override
public void onAnimationStart(Animator animation) {
sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- String.format(getContext().getString(R.string.folder_opened),
- mContent.getCountX(), mContent.getCountY()));
+ mContent.getAccessibilityDescription());
mState = STATE_ANIMATING;
}
@Override
@@ -521,7 +513,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
onCompleteRunnable.run();
}
- setFocusOnFirstChild();
+ mContent.setFocusOnFirstChild();
}
});
openFolderAnim.start();
@@ -530,21 +522,40 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
if (mDragController.isDragging()) {
mDragController.forceTouchMove();
}
+
+ FolderPagedView pages = (FolderPagedView) mContent;
+ pages.verifyVisibleHighResIcons(pages.getNextPage());
}
public void beginExternalDrag(ShortcutInfo item) {
- setupContentForNumItems(getItemCount() + 1);
- findAndSetEmptyCells(item);
-
mCurrentDragInfo = item;
- mEmptyCell[0] = item.cellX;
- mEmptyCell[1] = item.cellY;
+ mEmptyCellRank = mContent.allocateRankForNewItem(item);
mIsExternalDrag = true;
-
mDragInProgress = true;
+
+ if (mContent.mIsSorted) {
+ mScrollPauseAlarm.setOnAlarmListener(null);
+ mScrollPauseAlarm.cancelAlarm();
+ mScrollPauseAlarm.setAlarm(SORTED_STICKY_REORDER_DELAY);
+ }
+
+ // Since this folder opened by another controller, it might not get onDrop or
+ // onDropComplete. Perform cleanup once drag-n-drop ends.
+ mDragController.addDragListener(this);
+ }
+
+ @Override
+ public void onDragStart(DragSource source, Object info, int dragAction) { }
+
+ @Override
+ public void onDragEnd() {
+ if (mIsExternalDrag && mDragInProgress) {
+ completeDragExit();
+ }
+ mDragController.removeDragListener(this);
}
- private void sendCustomAccessibilityEvent(int type, String text) {
+ @Thunk void sendCustomAccessibilityEvent(int type, String text) {
AccessibilityManager accessibilityManager = (AccessibilityManager)
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager.isEnabled()) {
@@ -555,13 +566,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
}
- 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);
@@ -597,174 +601,90 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
!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 View createAndAddShortcut(ShortcutInfo item) {
- final BubbleTextView textView =
- (BubbleTextView) mInflater.inflate(R.layout.folder_application, this, false);
- textView.applyFromShortcutInfo(item, mIconCache, false);
-
- textView.setOnClickListener(this);
- textView.setOnLongClickListener(this);
- textView.setOnFocusChangeListener(mFocusIndicatorHandler);
-
- // 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 null;
- }
- }
-
- 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 textView;
- }
-
public void onDragEnter(DragObject d) {
- mPreviousTargetCell[0] = -1;
- mPreviousTargetCell[1] = -1;
+ mPrevTargetRank = -1;
mOnExitAlarm.cancelAlarm();
+ // Get the area offset such that the folder only closes if half the drag icon width
+ // is outside the folder area
+ mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset;
}
OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) {
- realTimeReorder(mEmptyCell, mTargetCell);
+ mContent.realTimeReorder(mEmptyCellRank, mTargetRank);
+ mEmptyCellRank = mTargetRank;
}
};
- boolean readingOrderGreaterThan(int[] v1, int[] v2) {
- if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
- return true;
- } else {
- return false;
- }
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
- 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;
- }
- }
- }
- }
+ @Override
+ public void onDragOver(DragObject d) {
+ onDragOver(d, REORDER_DELAY);
}
- public boolean isLayoutRtl() {
- return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ private int getTargetRank(DragObject d, float[] recycle) {
+ recycle = d.getVisualCenter(recycle);
+ return mContent.findNearestArea(
+ (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop());
}
- public void onDragOver(DragObject d) {
- final DragView dragView = d.dragView;
- final int scrollOffset = mScrollView.getScrollY();
- final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null);
- r[0] -= getPaddingLeft();
- r[1] -= getPaddingTop();
-
- final long downTime = SystemClock.uptimeMillis();
- final MotionEvent translatedEv = MotionEvent.obtain(
- downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0);
-
- if (!mAutoScrollHelper.isEnabled()) {
- mAutoScrollHelper.setEnabled(true);
+ @Thunk void onDragOver(DragObject d, int reorderDelay) {
+ if (mScrollPauseAlarm.alarmPending()) {
+ return;
}
+ final float[] r = new float[2];
+ mTargetRank = getTargetRank(d, r);
- final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv);
- translatedEv.recycle();
-
- if (handled) {
+ if (mTargetRank != mPrevTargetRank) {
mReorderAlarm.cancelAlarm();
+ mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
+ mReorderAlarm.setAlarm(REORDER_DELAY);
+ mPrevTargetRank = mTargetRank;
+ }
+
+ float x = r[0];
+ int currentPage = mContent.getNextPage();
+
+ float cellOverlap = mContent.getCurrentCellLayout().getCellWidth()
+ * ICON_OVERSCROLL_WIDTH_FACTOR;
+ boolean isOutsideLeftEdge = x < cellOverlap;
+ boolean isOutsideRightEdge = x > (getWidth() - cellOverlap);
+
+ if (currentPage > 0 && (mContent.rtlLayout ? isOutsideRightEdge : isOutsideLeftEdge)) {
+ showScrollHint(DragController.SCROLL_LEFT, d);
+ } else if (currentPage < (mContent.getPageCount() - 1)
+ && (mContent.rtlLayout ? isOutsideLeftEdge : isOutsideRightEdge)) {
+ showScrollHint(DragController.SCROLL_RIGHT, d);
} else {
- mTargetCell = mContent.findNearestArea(
- (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
- if (isLayoutRtl()) {
- mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
- }
- if (mTargetCell[0] != mPreviousTargetCell[0]
- || mTargetCell[1] != mPreviousTargetCell[1]) {
- mReorderAlarm.cancelAlarm();
- mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
- mReorderAlarm.setAlarm(REORDER_DELAY);
- mPreviousTargetCell[0] = mTargetCell[0];
- mPreviousTargetCell[1] = mTargetCell[1];
+ mOnScrollHintAlarm.cancelAlarm();
+ if (mScrollHintDir != DragController.SCROLL_NONE) {
+ mContent.clearScrollHint();
+ mScrollHintDir = DragController.SCROLL_NONE;
}
}
}
- // 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;
+ private void showScrollHint(int direction, DragObject d) {
+ // Show scroll hint on the right
+ if (mScrollHintDir != direction) {
+ mContent.showScrollHint(direction);
+ mScrollHintDir = direction;
}
- // 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;
+ // Set alarm for when the hint is complete
+ if (!mOnScrollHintAlarm.alarmPending() || mCurrentScrollDir != direction) {
+ mCurrentScrollDir = direction;
+ mOnScrollHintAlarm.cancelAlarm();
+ mOnScrollHintAlarm.setOnAlarmListener(new OnScrollHintListener(d));
+ mOnScrollHintAlarm.setAlarm(SCROLL_HINT_DURATION);
- return res;
+ mReorderAlarm.cancelAlarm();
+ mTargetRank = mEmptyCellRank;
+ }
}
OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
@@ -774,17 +694,19 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
};
public void completeDragExit() {
- mLauncher.closeFolder();
+ if (mInfo.opened) {
+ mLauncher.closeFolder();
+ mRearrangeOnClose = true;
+ } else {
+ rearrangeChildren();
+ }
mCurrentDragInfo = null;
mCurrentDragView = null;
mSuppressOnAdd = false;
- mRearrangeOnClose = true;
mIsExternalDrag = false;
}
public void onDragExit(DragObject d) {
- // Exiting folder; stop the auto scroller.
- mAutoScrollHelper.setEnabled(false);
// 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) {
@@ -792,6 +714,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
}
mReorderAlarm.cancelAlarm();
+
+ mOnScrollHintAlarm.cancelAlarm();
+ mScrollPauseAlarm.cancelAlarm();
+ if (mScrollHintDir != DragController.SCROLL_NONE) {
+ mContent.clearScrollHint();
+ mScrollHintDir = DragController.SCROLL_NONE;
+ }
}
public void onDropCompleted(final View target, final DragObject d,
@@ -816,7 +745,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
replaceFolderWithFinalItem();
}
} else {
- setupContentForNumItems(getItemCount());
+ rearrangeChildren();
// The drag failed, we need to return the item to the folder
mFolderIcon.onDrop(d);
}
@@ -827,6 +756,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
if (!successfulDrop) {
mSuppressFolderDeletion = true;
}
+ mScrollPauseAlarm.cancelAlarm();
completeDragExit();
}
}
@@ -843,10 +773,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
updateItemLocationsInDatabaseBatch();
}
+ @Override
public void deferCompleteDropAfterUninstallActivity() {
mDeferDropAfterUninstall = true;
}
+ @Override
public void onUninstallActivityReturned(boolean success) {
mDeferDropAfterUninstall = false;
mUninstallSuccessful = success;
@@ -903,7 +835,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
View v = list.get(i);
ItemInfo info = (ItemInfo) v.getTag();
LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0,
- info.cellX, info.cellY, false);
+ info.cellX, info.cellY);
}
}
@@ -917,37 +849,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return true;
}
- private void setupContentDimensions(int count) {
- ArrayList<View> 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;
+ return mContent.isFull();
}
private void centerAboutIcon() {
@@ -957,13 +860,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
int height = getFolderHeight();
- float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
+ float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
- int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
+ int centerX = (int) (sTempRect.left + sTempRect.width() * scale / 2);
+ int centerY = (int) (sTempRect.top + sTempRect.height() * scale / 2);
int centeredLeft = centerX - width / 2;
int centeredTop = centerY - height / 2;
int currentPage = mLauncher.getWorkspace().getNextPage();
@@ -1016,18 +919,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
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();
- }
-
private int getContentAreaHeight() {
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
@@ -1035,7 +926,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
int maxContentAreaHeight = grid.availableHeightPx -
workspacePadding.top - workspacePadding.bottom -
- mFolderNameHeight;
+ mFooterHeight;
int height = Math.min(maxContentAreaHeight,
mContent.getDesiredHeight());
return Math.max(height, MIN_CONTENT_DIMEN);
@@ -1046,67 +937,58 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
private int getFolderHeight() {
- int height = getPaddingTop() + getPaddingBottom()
- + getContentAreaHeight() + mFolderNameHeight;
- return height;
+ return getFolderHeight(getContentAreaHeight());
+ }
+
+ private int getFolderHeight(int contentAreaHeight) {
+ return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
- int height = getFolderHeight();
- int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
- MeasureSpec.EXACTLY);
- int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
- MeasureSpec.EXACTLY);
-
- if (LauncherAppState.isDisableAllApps()) {
- // Don't cap the height of the content to allow scrolling.
- mContent.setFixedSize(getContentAreaWidth(), mContent.getDesiredHeight());
- } else {
- mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
- }
+ int contentWidth = getContentAreaWidth();
+ int contentHeight = getContentAreaHeight();
+
+ int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
+ int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
+
+ mContent.setFixedSize(contentWidth, contentHeight);
+ mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec);
+ mFooter.measure(contentAreaWidthSpec,
+ MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY));
- mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
- mFolderName.measure(contentAreaWidthSpec,
- MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
- setMeasuredDimension(width, height);
+ int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth;
+ int folderHeight = getFolderHeight(contentHeight);
+ setMeasuredDimension(folderWidth, folderHeight);
}
- private void arrangeChildren(ArrayList<View> list) {
- int[] vacant = new int[2];
- if (list == null) {
- list = getItemsInReadingOrder();
- }
- mContent.removeAllViews();
+ /**
+ * Rearranges the children based on their rank.
+ */
+ public void rearrangeChildren() {
+ rearrangeChildren(-1);
+ }
- 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);
- }
+ /**
+ * Rearranges the children based on their rank.
+ * @param itemCount if greater than the total children count, empty spaces are left at the end,
+ * otherwise it is ignored.
+ */
+ public void rearrangeChildren(int itemCount) {
+ ArrayList<View> views = getItemsInReadingOrder();
+ mContent.arrangeChildren(views, Math.max(itemCount, views.size()));
mItemsInvalidated = true;
}
- public int getItemCount() {
- return mContent.getShortcutsAndWidgets().getChildCount();
+ // TODO remove this once GSA code fix is submitted
+ public ViewGroup getContent() {
+ return (ViewGroup) mContent;
}
- public View getItemAt(int index) {
- return mContent.getShortcutsAndWidgets().getChildAt(index);
+ public int getItemCount() {
+ return mContent.getItemCount();
}
- private void onCloseComplete() {
+ @Thunk void onCloseComplete() {
DragLayer parent = (DragLayer) getParent();
if (parent != null) {
parent.removeView(this);
@@ -1116,7 +998,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mFolderIcon.requestFocus();
if (mRearrangeOnClose) {
- setupContentForNumItems(getItemCount());
+ rearrangeChildren();
mRearrangeOnClose = false;
}
if (getItemCount() <= 1) {
@@ -1129,7 +1011,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mSuppressFolderDeletion = false;
}
- private void replaceFolderWithFinalItem() {
+ @Thunk void replaceFolderWithFinalItem() {
// Add the last remaining child to the workspace in place of the folder
Runnable onCompleteRunnable = new Runnable() {
@Override
@@ -1166,7 +1048,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
}
};
- View finalChild = getItemAt(0);
+ View finalChild = mContent.getLastItem();
if (finalChild != null) {
mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
} else {
@@ -1181,9 +1063,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// 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);
+ public void updateTextViewFocus() {
+ View lastChild = mContent.getLastItem();
if (lastChild != null) {
mFolderName.setNextFocusDownId(lastChild.getId());
mFolderName.setNextFocusRightId(lastChild.getId());
@@ -1208,12 +1089,24 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
};
}
+ // If the icon was dropped while the page was being scrolled, we need to compute
+ // the target location again such that the icon is placed of the final page.
+ if (!mContent.rankOnCurrentPage(mEmptyCellRank)) {
+ // Reorder again.
+ mTargetRank = getTargetRank(d, null);
+
+ // Rearrange items immediately.
+ mReorderAlarmListener.onAlarm(mReorderAlarm);
+
+ mOnScrollHintAlarm.cancelAlarm();
+ mScrollPauseAlarm.cancelAlarm();
+ }
+ mContent.completePendingPageChanges();
+
View currentDragView;
ShortcutInfo si = mCurrentDragInfo;
if (mIsExternalDrag) {
- si.cellX = mEmptyCell[0];
- si.cellY = mEmptyCell[1];
-
+ currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
// Actually move the item in the database if it was an external drag. Call this
// before creating the view, so that ShortcutInfo is updated appropriately.
LauncherModel.addOrMoveItemInDatabase(
@@ -1224,14 +1117,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
updateItemLocationsInDatabaseBatch();
}
mIsExternalDrag = false;
-
- currentDragView = createAndAddShortcut(si);
} else {
currentDragView = mCurrentDragView;
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams();
- si.cellX = lp.cellX = mEmptyCell[0];
- si.cellX = lp.cellY = mEmptyCell[1];
- mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true);
+ mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
}
if (d.dragView.hasDrawn()) {
@@ -1250,7 +1138,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
currentDragView.setVisibility(VISIBLE);
}
mItemsInvalidated = true;
- setupContentDimensions(getItemCount());
+ rearrangeChildren();
// Temporarily suppress the listener, as we did all the work already here.
mSuppressOnAdd = true;
@@ -1258,6 +1146,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mSuppressOnAdd = false;
// Clear the drag info, as it is no longer being dragged.
mCurrentDragInfo = null;
+ mDragInProgress = false;
}
// This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1273,16 +1162,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
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);
+ mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem(item));
+ mItemsInvalidated = true;
LauncherModel.addOrMoveItemInDatabase(
mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
}
@@ -1293,27 +1177,25 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// 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);
+ mContent.removeItem(v);
if (mState == STATE_ANIMATING) {
mRearrangeOnClose = true;
} else {
- setupContentForNumItems(getItemCount());
+ rearrangeChildren();
}
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;
- }
+ private View getViewForInfo(final ShortcutInfo item) {
+ return mContent.iterateOverItems(new ItemOperator() {
+
+ @Override
+ public boolean evaluate(ItemInfo info, View view, View parent) {
+ return info == item;
}
- }
- return null;
+ });
}
public void onItemsChanged() {
@@ -1326,14 +1208,14 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public ArrayList<View> getItemsInReadingOrder() {
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) {
- mItemsInReadingOrder.add(v);
- }
+ mContent.iterateOverItems(new ItemOperator() {
+
+ @Override
+ public boolean evaluate(ItemInfo info, View view, View parent) {
+ mItemsInReadingOrder.add(view);
+ return false;
}
- }
+ });
mItemsInvalidated = false;
}
return mItemsInReadingOrder;
@@ -1352,5 +1234,56 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
@Override
public void getHitRectRelativeToDragLayer(Rect outRect) {
getHitRect(outRect);
+ outRect.left -= mScrollAreaOffset;
+ outRect.right += mScrollAreaOffset;
+ }
+
+ private class OnScrollHintListener implements OnAlarmListener {
+
+ private final DragObject mDragObject;
+
+ OnScrollHintListener(DragObject object) {
+ mDragObject = object;
+ }
+
+ /**
+ * Scroll hint has been shown long enough. Now scroll to appropriate page.
+ */
+ @Override
+ public void onAlarm(Alarm alarm) {
+ if (mCurrentScrollDir == DragController.SCROLL_LEFT) {
+ mContent.scrollLeft();
+ mScrollHintDir = DragController.SCROLL_NONE;
+ } else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) {
+ mContent.scrollRight();
+ mScrollHintDir = DragController.SCROLL_NONE;
+ } else {
+ // This should not happen
+ return;
+ }
+ mCurrentScrollDir = DragController.SCROLL_NONE;
+
+ // Pause drag event until the scrolling is finished
+ mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject));
+ mScrollPauseAlarm.setAlarm(DragController.RESCROLL_DELAY);
+ }
+ }
+
+ private class OnScrollFinishedListener implements OnAlarmListener {
+
+ private final DragObject mDragObject;
+
+ OnScrollFinishedListener(DragObject object) {
+ mDragObject = object;
+ }
+
+ /**
+ * Page scroll is complete.
+ */
+ @Override
+ public void onAlarm(Alarm alarm) {
+ // Reorder immediately on page change.
+ onDragOver(mDragObject, 1);
+ }
}
}
diff --git a/src/com/android/launcher3/FolderAutoScrollHelper.java b/src/com/android/launcher3/FolderAutoScrollHelper.java
deleted file mode 100644
index 40e888464..000000000
--- a/src/com/android/launcher3/FolderAutoScrollHelper.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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.launcher3;
-
-import android.support.v4.widget.AutoScrollHelper;
-import android.widget.ScrollView;
-
-/**
- * An implementation of {@link AutoScrollHelper} that knows how to scroll
- * through a {@link Folder}.
- */
-public class FolderAutoScrollHelper extends AutoScrollHelper {
- private static final float MAX_SCROLL_VELOCITY = 1500f;
-
- private final ScrollView mTarget;
-
- public FolderAutoScrollHelper(ScrollView target) {
- super(target);
-
- mTarget = target;
-
- setActivationDelay(0);
- setEdgeType(EDGE_TYPE_INSIDE_EXTEND);
- setExclusive(true);
- setMaximumVelocity(MAX_SCROLL_VELOCITY, MAX_SCROLL_VELOCITY);
- setRampDownDuration(0);
- setRampUpDuration(0);
- }
-
- @Override
- public void scrollTargetBy(int deltaX, int deltaY) {
- mTarget.scrollBy(deltaX, deltaY);
- }
-
- @Override
- public boolean canTargetScrollHorizontally(int direction) {
- // List do not scroll horizontally.
- return false;
- }
-
- @Override
- public boolean canTargetScrollVertically(int direction) {
- return mTarget.canScrollVertically(direction);
- }
-} \ No newline at end of file
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index a3e82959a..f5836c295 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -43,6 +43,7 @@ import android.widget.TextView;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -50,15 +51,15 @@ import java.util.ArrayList;
* An icon that can appear on in the workspace representing an {@link UserFolder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener {
- private Launcher mLauncher;
- private Folder mFolder;
+ @Thunk Launcher mLauncher;
+ @Thunk Folder mFolder;
private FolderInfo mInfo;
- private static boolean sStaticValuesDirty = true;
+ @Thunk static boolean sStaticValuesDirty = true;
private CheckLongPressHelper mLongPressHelper;
// The number of icons to display in the
- private static final int NUM_ITEMS_IN_PREVIEW = 3;
+ public 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;
@@ -88,8 +89,8 @@ public class FolderIcon extends FrameLayout implements FolderListener {
public static Drawable sSharedFolderLeaveBehind = null;
- private ImageView mPreviewBackground;
- private BubbleTextView mFolderName;
+ @Thunk ImageView mPreviewBackground;
+ @Thunk BubbleTextView mFolderName;
FolderRingAnimator mFolderRingAnimator = null;
@@ -109,11 +110,11 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private float mSlop;
private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
- private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
- private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
+ @Thunk PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+ @Thunk ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
private Alarm mOpenAlarm = new Alarm();
- private ItemInfo mDragInfo;
+ @Thunk ItemInfo mDragInfo;
public FolderIcon(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -192,7 +193,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
public static class FolderRingAnimator {
public int mCellX;
public int mCellY;
- private CellLayout mCellLayout;
+ @Thunk CellLayout mCellLayout;
public float mOuterRingSize;
public float mInnerRingSize;
public FolderIcon mFolderIcon = null;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 85a792f4b..80b156413 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -29,19 +29,33 @@ import java.util.Arrays;
*/
public class FolderInfo extends ItemInfo {
+ public static final int NO_FLAGS = 0x00000000;
+
+ /**
+ * The folder is locked in sorted mode
+ */
+ public static final int FLAG_ITEMS_SORTED = 0x00000001;
+
+ /**
+ * It is a work folder
+ */
+ public static final int FLAG_WORK_FOLDER = 0x00000002;
+
/**
* Whether this folder has been opened
*/
boolean opened;
+ public int options;
+
/**
* The apps and shortcuts
*/
- ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
+ public ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
- FolderInfo() {
+ public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
user = UserHandleCompat.myUserHandle();
}
@@ -83,6 +97,8 @@ public class FolderInfo extends ItemInfo {
void onAddToDatabase(Context context, ContentValues values) {
super.onAddToDatabase(context, values);
values.put(LauncherSettings.Favorites.TITLE, title.toString());
+ values.put(LauncherSettings.Favorites.OPTIONS, options);
+
}
void addListener(FolderListener listener) {
@@ -121,4 +137,25 @@ public class FolderInfo extends ItemInfo {
+ " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
+ " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
}
+
+ public boolean hasOption(int optionFlag) {
+ return (options & optionFlag) != 0;
+ }
+
+ /**
+ * @param option flag to set or clear
+ * @param isEnabled whether to set or clear the flag
+ * @param context if not null, save changes to the db.
+ */
+ public void setOption(int option, boolean isEnabled, Context context) {
+ int oldOptions = options;
+ if (isEnabled) {
+ options |= option;
+ } else {
+ options &= ~option;
+ }
+ if (context != null && oldOptions != options) {
+ LauncherModel.updateItemInDatabase(context, this);
+ }
+ }
}
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
new file mode 100644
index 000000000..617489271
--- /dev/null
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -0,0 +1,842 @@
+/**
+ * Copyright (C) 2015 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.launcher3;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Switch;
+
+import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
+import com.android.launcher3.PageIndicator.PageMarkerResources;
+import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.util.Thunk;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class FolderPagedView extends PagedView {
+
+ private static final String TAG = "FolderPagedView";
+
+ private static final boolean ALLOW_FOLDER_SCROLL = true;
+
+ // To enable this flag, user_folder.xml needs to be modified to add sort button.
+ private static final boolean ALLOW_ITEM_SORTING = false;
+
+ private static final int REORDER_ANIMATION_DURATION = 230;
+ private static final int START_VIEW_REORDER_DELAY = 30;
+ private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
+
+ private static final int SPAN_TO_PAGE_DURATION = 350;
+ private static final int SORT_ANIM_HIDE_DURATION = 130;
+ private static final int SORT_ANIM_SHOW_DURATION = 160;
+
+ /**
+ * Fraction of the width to scroll when showing the next page hint.
+ */
+ private static final float SCROLL_HINT_FRACTION = 0.07f;
+
+ private static final int[] sTempPosArray = new int[2];
+
+ // TODO: Remove this restriction
+ private static final int MAX_ITEMS_PER_PAGE = 4;
+
+ public final boolean rtlLayout;
+
+ private final LayoutInflater mInflater;
+ private final IconCache mIconCache;
+
+ @Thunk final HashMap<View, Runnable> mPendingAnimations = new HashMap<>();
+
+ private final int mMaxCountX;
+ private final int mMaxCountY;
+ private final int mMaxItemsPerPage;
+
+ private int mAllocatedContentSize;
+ private int mGridCountX;
+ private int mGridCountY;
+
+ private Folder mFolder;
+ private FocusIndicatorView mFocusIndicatorView;
+ private PagedFolderKeyEventListener mKeyListener;
+
+ private View mSortButton;
+ private Switch mSortSwitch;
+ private View mPageIndicator;
+
+ private boolean mSortOperationPending;
+ boolean mIsSorted;
+
+ public FolderPagedView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LauncherAppState app = LauncherAppState.getInstance();
+ setDataIsReady();
+
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ if (ALLOW_FOLDER_SCROLL) {
+ mMaxCountX = Math.min((int) grid.numColumns, MAX_ITEMS_PER_PAGE);
+ mMaxCountY = Math.min((int) grid.numRows, MAX_ITEMS_PER_PAGE);
+ } else {
+ mMaxCountX = (int) grid.numColumns;
+ mMaxCountY = (int) grid.numRows;
+ }
+
+ mMaxItemsPerPage = mMaxCountX * mMaxCountY;
+
+ mInflater = LayoutInflater.from(context);
+ mIconCache = app.getIconCache();
+
+ rtlLayout = getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ }
+
+ public void setFolder(Folder folder) {
+ mFolder = folder;
+ mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
+ mKeyListener = new PagedFolderKeyEventListener(folder);
+ mPageIndicator = folder.findViewById(R.id.folder_page_indicator);
+
+ if (ALLOW_ITEM_SORTING) {
+ // Initialize {@link #mSortSwitch} and {@link #mSortButton}.
+ }
+ }
+
+ /**
+ * Called when sort button is clicked.
+ */
+ private void onSortClicked() {
+ if (mSortOperationPending) {
+ return;
+ }
+ if (mIsSorted) {
+ setIsSorted(false, true);
+ } else {
+ mSortOperationPending = true;
+ doSort();
+ }
+ }
+
+ private void setIsSorted(boolean isSorted, boolean saveChanges) {
+ mIsSorted = isSorted;
+ if (ALLOW_ITEM_SORTING) {
+ mSortSwitch.setChecked(isSorted);
+ mFolder.mInfo.setOption(FolderInfo.FLAG_ITEMS_SORTED, isSorted,
+ saveChanges ? mFolder.mLauncher : null);
+ }
+ }
+
+ /**
+ * Sorts the contents of the folder and animates the icons on the first page to reflect
+ * the changes.
+ * Steps:
+ * 1. Scroll to first page
+ * 2. Sort all icons in one go
+ * 3. Re-apply the old IconInfos on the first page (so that there is no instant change)
+ * 4. Animate each view individually to reflect the new icon.
+ */
+ private void doSort() {
+ if (!mSortOperationPending) {
+ return;
+ }
+ if (getNextPage() != 0) {
+ snapToPage(0, SPAN_TO_PAGE_DURATION, new DecelerateInterpolator());
+ return;
+ }
+
+ mSortOperationPending = false;
+ ShortcutInfo[][] oldItems = new ShortcutInfo[mGridCountX][mGridCountY];
+ CellLayout currentPage = getCurrentCellLayout();
+ for (int x = 0; x < mGridCountX; x++) {
+ for (int y = 0; y < mGridCountY; y++) {
+ View v = currentPage.getChildAt(x, y);
+ if (v != null) {
+ oldItems[x][y] = (ShortcutInfo) v.getTag();
+ }
+ }
+ }
+
+ ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder());
+ Collections.sort(views, new ViewComparator());
+ arrangeChildren(views, views.size());
+
+ int delay = 0;
+ float delayAmount = START_VIEW_REORDER_DELAY;
+ final Interpolator hideInterpolator = new DecelerateInterpolator(2);
+ final Interpolator showInterpolator = new OvershootInterpolator(0.8f);
+
+ currentPage = getCurrentCellLayout();
+ for (int x = 0; x < mGridCountX; x++) {
+ for (int y = 0; y < mGridCountY; y++) {
+ final BubbleTextView v = (BubbleTextView) currentPage.getChildAt(x, y);
+ if (v != null) {
+ final ShortcutInfo info = (ShortcutInfo) v.getTag();
+ final Runnable clearPending = new Runnable() {
+
+ @Override
+ public void run() {
+ mPendingAnimations.remove(v);
+ v.setScaleX(1);
+ v.setScaleY(1);
+ }
+ };
+ if (oldItems[x][y] == null) {
+ v.setScaleX(0);
+ v.setScaleY(0);
+ v.animate().setDuration(SORT_ANIM_SHOW_DURATION)
+ .setStartDelay(SORT_ANIM_HIDE_DURATION + delay)
+ .scaleX(1).scaleY(1).setInterpolator(showInterpolator)
+ .withEndAction(clearPending);
+ mPendingAnimations.put(v, clearPending);
+ } else {
+ // Apply the old iconInfo so that there is no sudden change.
+ v.applyFromShortcutInfo(oldItems[x][y], mIconCache, false);
+ v.animate().setStartDelay(delay).setDuration(SORT_ANIM_HIDE_DURATION)
+ .scaleX(0).scaleY(0)
+ .setInterpolator(hideInterpolator)
+ .withEndAction(new Runnable() {
+
+ @Override
+ public void run() {
+ // Apply the new iconInfo as part of the animation.
+ v.applyFromShortcutInfo(info, mIconCache, false);
+ v.animate().scaleX(1).scaleY(1)
+ .setDuration(SORT_ANIM_SHOW_DURATION).setStartDelay(0)
+ .setInterpolator(showInterpolator)
+ .withEndAction(clearPending);
+ }
+ });
+ mPendingAnimations.put(v, new Runnable() {
+
+ @Override
+ public void run() {
+ clearPending.run();
+ v.applyFromShortcutInfo(info, mIconCache, false);
+ }
+ });
+ }
+ delay += delayAmount;
+ delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+ }
+ }
+ }
+
+ setIsSorted(true, true);
+ }
+
+ /**
+ * Sets up the grid size such that {@param count} items can fit in the grid.
+ * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
+ * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
+ */
+ private void setupContentDimensions(int count) {
+ mAllocatedContentSize = count;
+ boolean done;
+ if (count >= mMaxItemsPerPage) {
+ mGridCountX = mMaxCountX;
+ mGridCountY = mMaxCountY;
+ done = true;
+ } else {
+ done = false;
+ }
+
+ while (!done) {
+ int oldCountX = mGridCountX;
+ int oldCountY = mGridCountY;
+ if (mGridCountX * mGridCountY < count) {
+ // Current grid is too small, expand it
+ if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) {
+ mGridCountX++;
+ } else if (mGridCountY < mMaxCountY) {
+ mGridCountY++;
+ }
+ if (mGridCountY == 0) mGridCountY++;
+ } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) {
+ mGridCountY = Math.max(0, mGridCountY - 1);
+ } else if ((mGridCountX - 1) * mGridCountY >= count) {
+ mGridCountX = Math.max(0, mGridCountX - 1);
+ }
+ done = mGridCountX == oldCountX && mGridCountY == oldCountY;
+ }
+
+ // Update grid size
+ for (int i = getPageCount() - 1; i >= 0; i--) {
+ getPageAt(i).setGridSize(mGridCountX, mGridCountY);
+ }
+ }
+
+ /**
+ * Binds items to the layout.
+ * @return list of items that could not be bound, probably because we hit the max size limit.
+ */
+ public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
+ mIsSorted = ALLOW_ITEM_SORTING && mFolder.mInfo.hasOption(FolderInfo.FLAG_ITEMS_SORTED);
+ ArrayList<View> icons = new ArrayList<View>();
+ ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
+
+ for (ShortcutInfo item : items) {
+ if (!ALLOW_FOLDER_SCROLL && icons.size() >= mMaxItemsPerPage) {
+ extra.add(item);
+ } else {
+ icons.add(createNewView(item));
+ }
+ }
+ arrangeChildren(icons, icons.size(), false);
+ return extra;
+ }
+
+ /**
+ * Create space for a new item at the end, and returns the rank for that item.
+ * Also sets the current page to the last page.
+ */
+ public int allocateRankForNewItem(ShortcutInfo info) {
+ int rank = getItemCount();
+ ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder());
+ if (ALLOW_ITEM_SORTING && mIsSorted) {
+ View tmp = new View(getContext());
+ tmp.setTag(info);
+ int index = Collections.binarySearch(views, tmp, new ViewComparator());
+ if (index < 0) {
+ rank = -index - 1;
+ } else {
+ // Item with same name already exists.
+ // We will just insert it before that item.
+ rank = index;
+ }
+
+ }
+
+ views.add(rank, null);
+ arrangeChildren(views, views.size(), false);
+ setCurrentPage(rank / mMaxItemsPerPage);
+ return rank;
+ }
+
+ public View createAndAddViewForRank(ShortcutInfo item, int rank) {
+ View icon = createNewView(item);
+ addViewForRank(icon, item, rank);
+ return icon;
+ }
+
+ /**
+ * Adds the {@param view} to the layout based on {@param rank} and updated the position
+ * related attributes. It assumes that {@param item} is already attached to the view.
+ */
+ public void addViewForRank(View view, ShortcutInfo item, int rank) {
+ int pagePos = rank % mMaxItemsPerPage;
+ int pageNo = rank / mMaxItemsPerPage;
+
+ item.rank = rank;
+ item.cellX = pagePos % mGridCountX;
+ item.cellY = pagePos / mGridCountX;
+
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+ lp.cellX = item.cellX;
+ lp.cellY = item.cellY;
+ getPageAt(pageNo).addViewToCellLayout(
+ view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+ }
+
+ @SuppressLint("InflateParams")
+ private View createNewView(ShortcutInfo item) {
+ final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
+ R.layout.folder_application, null, false);
+ textView.applyFromShortcutInfo(item, mIconCache, false);
+ textView.setOnClickListener(mFolder);
+ textView.setOnLongClickListener(mFolder);
+ textView.setOnFocusChangeListener(mFocusIndicatorView);
+ textView.setOnKeyListener(mKeyListener);
+
+ textView.setLayoutParams(new CellLayout.LayoutParams(
+ item.cellX, item.cellY, item.spanX, item.spanY));
+ return textView;
+ }
+
+ @Override
+ public CellLayout getPageAt(int index) {
+ return (CellLayout) getChildAt(index);
+ }
+
+ public void removeCellLayoutView(View view) {
+ for (int i = getChildCount() - 1; i >= 0; i --) {
+ getPageAt(i).removeView(view);
+ }
+ }
+
+ public CellLayout getCurrentCellLayout() {
+ return getPageAt(getNextPage());
+ }
+
+ private CellLayout createAndAddNewPage() {
+ DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ CellLayout page = new CellLayout(getContext());
+ page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+ page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+ page.setInvertIfRtl(true);
+ page.setGridSize(mGridCountX, mGridCountY);
+
+ LayoutParams lp = generateDefaultLayoutParams();
+ lp.isFullScreenPage = true;
+ addView(page, -1, lp);
+ return page;
+ }
+
+ public void setFixedSize(int width, int height) {
+ for (int i = getChildCount() - 1; i >= 0; i --) {
+ ((CellLayout) getChildAt(i)).setFixedSize(width, height);
+ }
+ }
+
+ public void removeItem(View v) {
+ for (int i = getChildCount() - 1; i >= 0; i --) {
+ getPageAt(i).removeView(v);
+ }
+ }
+
+ /**
+ * Updates position and rank of all the children in the view.
+ * It essentially removes all views from all the pages and then adds them again in appropriate
+ * page.
+ *
+ * @param list the ordered list of children.
+ * @param itemCount if greater than the total children count, empty spaces are left
+ * at the end, otherwise it is ignored.
+ *
+ */
+ public void arrangeChildren(ArrayList<View> list, int itemCount) {
+ arrangeChildren(list, itemCount, true);
+ }
+
+ private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
+ ArrayList<CellLayout> pages = new ArrayList<CellLayout>();
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout page = (CellLayout) getChildAt(i);
+ page.removeAllViews();
+ pages.add(page);
+ }
+ setupContentDimensions(itemCount);
+
+ Iterator<CellLayout> pageItr = pages.iterator();
+ CellLayout currentPage = null;
+
+ int position = 0;
+ int newX, newY, rank;
+
+ boolean isSorted = mIsSorted;
+
+ ViewComparator comparator = new ViewComparator();
+ View lastView = null;
+ rank = 0;
+ for (int i = 0; i < itemCount; i++) {
+ View v = list.size() > i ? list.get(i) : null;
+ if (currentPage == null || position >= mMaxItemsPerPage) {
+ // Next page
+ if (pageItr.hasNext()) {
+ currentPage = pageItr.next();
+ } else {
+ currentPage = createAndAddNewPage();
+ }
+ position = 0;
+ }
+
+ if (v != null) {
+ if (lastView != null) {
+ isSorted &= comparator.compare(lastView, v) <= 0;
+ }
+
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+ newX = position % mGridCountX;
+ newY = position / mGridCountX;
+ ItemInfo info = (ItemInfo) v.getTag();
+ if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
+ info.cellX = newX;
+ info.cellY = newY;
+ info.rank = rank;
+ if (saveChanges) {
+ LauncherModel.addOrMoveItemInDatabase(getContext(), info,
+ mFolder.mInfo.id, 0, info.cellX, info.cellY);
+ }
+ }
+ lp.cellX = info.cellX;
+ lp.cellY = info.cellY;
+ currentPage.addViewToCellLayout(
+ v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+ }
+
+ lastView = v;
+ rank ++;
+ position++;
+ }
+
+ // Remove extra views.
+ boolean removed = false;
+ while (pageItr.hasNext()) {
+ removeView(pageItr.next());
+ removed = true;
+ }
+ if (removed) {
+ setCurrentPage(0);
+ }
+
+ setEnableOverscroll(getPageCount() > 1);
+
+ // Update footer
+ if (ALLOW_ITEM_SORTING) {
+ setIsSorted(isSorted, saveChanges);
+ if (getPageCount() > 1) {
+ mPageIndicator.setVisibility(View.VISIBLE);
+ mSortButton.setVisibility(View.VISIBLE);
+ mFolder.mFolderName.setGravity(rtlLayout ? Gravity.RIGHT : Gravity.LEFT);
+ } else {
+ mPageIndicator.setVisibility(View.GONE);
+ mSortButton.setVisibility(View.GONE);
+ mFolder.mFolderName.setGravity(Gravity.CENTER_HORIZONTAL);
+ }
+ } else {
+ int indicatorVisibility = mPageIndicator.getVisibility();
+ mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
+ if (indicatorVisibility != mPageIndicator.getVisibility()) {
+ mFolder.updateFooterHeight();
+ }
+ }
+ }
+
+ @Override
+ protected void loadAssociatedPages(int page, boolean immediateAndOnly) { }
+
+ @Override
+ public void syncPages() { }
+
+ @Override
+ public void syncPageItems(int page, boolean immediate) { }
+
+ public int getDesiredWidth() {
+ return getPageCount() > 0 ? getPageAt(0).getDesiredWidth() : 0;
+ }
+
+ public int getDesiredHeight() {
+ return getPageCount() > 0 ? getPageAt(0).getDesiredHeight() : 0;
+ }
+
+ public int getItemCount() {
+ int lastPageIndex = getChildCount() - 1;
+ if (lastPageIndex < 0) {
+ // If there are no pages, nothing has yet been added to the folder.
+ return 0;
+ }
+ return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
+ + lastPageIndex * mMaxItemsPerPage;
+ }
+
+ /**
+ * @return the rank of the cell nearest to the provided pixel position.
+ */
+ public int findNearestArea(int pixelX, int pixelY) {
+ int pageIndex = getNextPage();
+ CellLayout page = getPageAt(pageIndex);
+ page.findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray);
+ if (mFolder.isLayoutRtl()) {
+ sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1;
+ }
+ return Math.min(mAllocatedContentSize - 1,
+ pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]);
+ }
+
+ @Override
+ protected PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+ return new PageMarkerResources(R.drawable.ic_pageindicator_current_folder,
+ R.drawable.ic_pageindicator_default_folder);
+ }
+
+ public boolean isFull() {
+ return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
+ }
+
+ public View getLastItem() {
+ if (getChildCount() < 1) {
+ return null;
+ }
+ ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+ int lastRank = lastContainer.getChildCount() - 1;
+ if (mGridCountX > 0) {
+ return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+ } else {
+ return lastContainer.getChildAt(lastRank);
+ }
+ }
+
+ /**
+ * Iterates over all its items in a reading order.
+ * @return the view for which the operator returned true.
+ */
+ public View iterateOverItems(ItemOperator op) {
+ for (int k = 0 ; k < getChildCount(); k++) {
+ CellLayout page = getPageAt(k);
+ for (int j = 0; j < page.getCountY(); j++) {
+ for (int i = 0; i < page.getCountX(); i++) {
+ View v = page.getChildAt(i, j);
+ if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) {
+ return v;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public String getAccessibilityDescription() {
+ return String.format(getContext().getString(R.string.folder_opened),
+ mGridCountX, mGridCountY);
+ }
+
+ /**
+ * Sets the focus on the first visible child.
+ */
+ public void setFocusOnFirstChild() {
+ View firstChild = getCurrentCellLayout().getChildAt(0, 0);
+ if (firstChild != null) {
+ firstChild.requestFocus();
+ }
+ }
+
+ @Override
+ protected void notifyPageSwitchListener() {
+ super.notifyPageSwitchListener();
+ if (mFolder != null) {
+ mFolder.updateTextViewFocus();
+ }
+ if (ALLOW_ITEM_SORTING && mSortOperationPending && getNextPage() == 0) {
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ if (mSortOperationPending) {
+ doSort();
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Scrolls the current view by a fraction
+ */
+ public void showScrollHint(int direction) {
+ float fraction = (direction == DragController.SCROLL_LEFT) ^ rtlLayout
+ ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
+ int hint = (int) (fraction * getWidth());
+ int scroll = getScrollForPage(getNextPage()) + hint;
+ int delta = scroll - mUnboundedScrollX;
+ if (delta != 0) {
+ mScroller.setInterpolator(new DecelerateInterpolator());
+ mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, Folder.SCROLL_HINT_DURATION);
+ invalidate();
+ }
+ }
+
+ public void clearScrollHint() {
+ if (mUnboundedScrollX != getScrollForPage(getNextPage())) {
+ snapToPage(getNextPage());
+ }
+ }
+
+ /**
+ * Finish animation all the views which are animating across pages
+ */
+ public void completePendingPageChanges() {
+ if (!mPendingAnimations.isEmpty()) {
+ HashMap<View, Runnable> pendingViews = new HashMap<>(mPendingAnimations);
+ for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
+ e.getKey().animate().cancel();
+ e.getValue().run();
+ }
+ }
+ }
+
+ public boolean rankOnCurrentPage(int rank) {
+ int p = rank / mMaxItemsPerPage;
+ return p == getNextPage();
+ }
+
+ @Override
+ protected void onPageBeginMoving() {
+ super.onPageBeginMoving();
+ getVisiblePages(sTempPosArray);
+ for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
+ verifyVisibleHighResIcons(i);
+ }
+ }
+
+ /**
+ * Ensures that all the icons on the given page are of high-res
+ */
+ public void verifyVisibleHighResIcons(int pageNo) {
+ CellLayout page = getPageAt(pageNo);
+ if (page != null) {
+ ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
+ for (int i = parent.getChildCount() - 1; i >= 0; i--) {
+ ((BubbleTextView) parent.getChildAt(i)).verifyHighRes();
+ }
+ }
+ }
+
+ /**
+ * Reorders the items such that the {@param empty} spot moves to {@param target}
+ */
+ public void realTimeReorder(int empty, int target) {
+ completePendingPageChanges();
+ int delay = 0;
+ float delayAmount = START_VIEW_REORDER_DELAY;
+
+ // Animation only happens on the current page.
+ int pageToAnimate = getNextPage();
+
+ int pageT = target / mMaxItemsPerPage;
+ int pagePosT = target % mMaxItemsPerPage;
+
+ if (pageT != pageToAnimate) {
+ Log.e(TAG, "Cannot animate when the target cell is invisible");
+ }
+ int pagePosE = empty % mMaxItemsPerPage;
+ int pageE = empty / mMaxItemsPerPage;
+
+ int startPos, endPos;
+ int moveStart, moveEnd;
+ int direction;
+
+ if (target == empty) {
+ // No animation
+ return;
+ } else if (target > empty) {
+ // Items will move backwards to make room for the empty cell.
+ direction = 1;
+
+ // If empty cell is in a different page, move them instantly.
+ if (pageE < pageToAnimate) {
+ moveStart = empty;
+ // Instantly move the first item in the current page.
+ moveEnd = pageToAnimate * mMaxItemsPerPage;
+ // Animate the 2nd item in the current page, as the first item was already moved to
+ // the last page.
+ startPos = 0;
+ } else {
+ moveStart = moveEnd = -1;
+ startPos = pagePosE;
+ }
+
+ endPos = pagePosT;
+ } else {
+ // The items will move forward.
+ direction = -1;
+
+ if (pageE > pageToAnimate) {
+ // Move the items immediately.
+ moveStart = empty;
+ // Instantly move the last item in the current page.
+ moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
+
+ // Animations start with the second last item in the page
+ startPos = mMaxItemsPerPage - 1;
+ } else {
+ moveStart = moveEnd = -1;
+ startPos = pagePosE;
+ }
+
+ endPos = pagePosT;
+ }
+
+ // Instant moving views.
+ while (moveStart != moveEnd) {
+ int rankToMove = moveStart + direction;
+ int p = rankToMove / mMaxItemsPerPage;
+ int pagePos = rankToMove % mMaxItemsPerPage;
+ int x = pagePos % mGridCountX;
+ int y = pagePos / mGridCountX;
+
+ final CellLayout page = getPageAt(p);
+ final View v = page.getChildAt(x, y);
+ if (v != null) {
+ if (pageToAnimate != p) {
+ page.removeView(v);
+ addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
+ } else {
+ // Do a fake animation before removing it.
+ final int newRank = moveStart;
+ final float oldTranslateX = v.getTranslationX();
+
+ Runnable endAction = new Runnable() {
+
+ @Override
+ public void run() {
+ mPendingAnimations.remove(v);
+ v.setTranslationX(oldTranslateX);
+ ((CellLayout) v.getParent().getParent()).removeView(v);
+ addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
+ }
+ };
+ v.animate()
+ .translationXBy((direction > 0 ^ rtlLayout) ? -v.getWidth() : v.getWidth())
+ .setDuration(REORDER_ANIMATION_DURATION)
+ .setStartDelay(0)
+ .withEndAction(endAction);
+ mPendingAnimations.put(v, endAction);
+ }
+ }
+ moveStart = rankToMove;
+ }
+
+ if ((endPos - startPos) * direction <= 0) {
+ // No animation
+ return;
+ }
+
+ CellLayout page = getPageAt(pageToAnimate);
+ for (int i = startPos; i != endPos; i += direction) {
+ int nextPos = i + direction;
+ View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
+ if (v != null) {
+ ((ItemInfo) v.getTag()).rank -= direction;
+ }
+ if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
+ delay += delayAmount;
+ delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+ }
+ }
+ }
+
+ private static class ViewComparator implements Comparator<View> {
+ private final Collator mCollator = Collator.getInstance();
+
+ @Override
+ public int compare(View lhs, View rhs) {
+ return mCollator.compare( ((ShortcutInfo) lhs.getTag()).title.toString(),
+ ((ShortcutInfo) rhs.getTag()).title.toString());
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b08272f36..b614bc628 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,24 +16,18 @@
package com.android.launcher3;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
-import java.util.ArrayList;
-
public class Hotseat extends FrameLayout {
- private static final String TAG = "Hotseat";
private CellLayout mContent;
@@ -86,19 +80,22 @@ public class Hotseat extends FrameLayout {
int getOrderInHotseat(int x, int y) {
return hasVerticalHotseat() ? (mContent.getCountY() - y - 1) : x;
}
+
/* Get the orientation specific coordinates given an invariant order in the hotseat. */
int getCellXFromOrder(int rank) {
return hasVerticalHotseat() ? 0 : rank;
}
+
int getCellYFromOrder(int rank) {
return hasVerticalHotseat() ? (mContent.getCountY() - (rank + 1)) : 0;
}
+
+ public int getAllAppsButtonRank() {
+ return mAllAppsButtonRank;
+ }
+
public boolean isAllAppsButtonRank(int rank) {
- if (LauncherAppState.isDisableAllApps()) {
- return false;
- } else {
- return rank == mAllAppsButtonRank;
- }
+ return rank == mAllAppsButtonRank;
}
/** This returns the coordinates of an app in a given cell, relative to the DragLayer */
@@ -141,35 +138,33 @@ public class Hotseat extends FrameLayout {
void resetLayout() {
mContent.removeAllViewsInLayout();
- if (!LauncherAppState.isDisableAllApps()) {
- // Add the Apps button
- Context context = getContext();
-
- LayoutInflater inflater = LayoutInflater.from(context);
- TextView allAppsButton = (TextView)
- inflater.inflate(R.layout.all_apps_button, mContent, false);
- Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
-
- Utilities.resizeIconDrawable(d);
- allAppsButton.setCompoundDrawables(null, d, null, null);
-
- allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
- allAppsButton.setOnKeyListener(new HotseatIconKeyEventListener());
- if (mLauncher != null) {
- allAppsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
- mLauncher.setAllAppsButton(allAppsButton);
- allAppsButton.setOnClickListener(mLauncher);
- allAppsButton.setOnFocusChangeListener(mLauncher.mFocusHandler);
- }
-
- // 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, allAppsButton.getId(), lp, true);
+ // Add the Apps button
+ Context context = getContext();
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ TextView allAppsButton = (TextView)
+ inflater.inflate(R.layout.all_apps_button, mContent, false);
+ Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+
+ Utilities.resizeIconDrawable(d);
+ allAppsButton.setCompoundDrawables(null, d, null, null);
+
+ allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
+ allAppsButton.setOnKeyListener(new HotseatIconKeyEventListener());
+ if (mLauncher != null) {
+ allAppsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+ mLauncher.setAllAppsButton(allAppsButton);
+ allAppsButton.setOnClickListener(mLauncher);
+ allAppsButton.setOnFocusChangeListener(mLauncher.mFocusHandler);
}
+
+ // 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, allAppsButton.getId(), lp, true);
}
@Override
@@ -181,55 +176,4 @@ public class Hotseat extends FrameLayout {
}
return false;
}
-
- void addAllAppsFolder(IconCache iconCache,
- ArrayList<AppInfo> allApps, ArrayList<ComponentName> onWorkspace,
- Launcher launcher, Workspace workspace) {
- if (LauncherAppState.isDisableAllApps()) {
- FolderInfo fi = new FolderInfo();
-
- fi.cellX = getCellXFromOrder(mAllAppsButtonRank);
- fi.cellY = getCellYFromOrder(mAllAppsButtonRank);
- fi.spanX = 1;
- fi.spanY = 1;
- fi.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
- fi.screenId = mAllAppsButtonRank;
- fi.itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
- fi.title = "More Apps";
- LauncherModel.addItemToDatabase(launcher, fi, fi.container, fi.screenId, fi.cellX,
- fi.cellY, false);
- FolderIcon folder = FolderIcon.fromXml(R.layout.folder_icon, launcher,
- getLayout(), fi, iconCache);
- workspace.addInScreen(folder, fi.container, fi.screenId, fi.cellX, fi.cellY,
- fi.spanX, fi.spanY);
-
- for (AppInfo info: allApps) {
- ComponentName cn = info.intent.getComponent();
- if (!onWorkspace.contains(cn)) {
- Log.d(TAG, "Adding to 'more apps': " + info.intent);
- ShortcutInfo si = info.makeShortcut();
- fi.add(si);
- }
- }
- }
- }
-
- void addAppsToAllAppsFolder(ArrayList<AppInfo> apps) {
- if (LauncherAppState.isDisableAllApps()) {
- View v = mContent.getChildAt(getCellXFromOrder(mAllAppsButtonRank), getCellYFromOrder(mAllAppsButtonRank));
- FolderIcon fi = null;
-
- if (v instanceof FolderIcon) {
- fi = (FolderIcon) v;
- } else {
- return;
- }
-
- FolderInfo info = fi.getFolderInfo();
- for (AppInfo a: apps) {
- ShortcutInfo si = a.makeShortcut();
- info.add(si);
- }
- }
- }
}
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5a0875b30..48b38f182 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -18,17 +18,23 @@ package com.android.launcher3;
import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
@@ -36,16 +42,15 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.PackageItemInfo;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
import java.util.Map.Entry;
/**
@@ -56,49 +61,34 @@ public class IconCache {
private static final String TAG = "Launcher.IconCache";
private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
- private static final String RESOURCE_FILE_PREFIX = "icon_";
// Empty class name is used for storing package default entry.
private static final String EMPTY_CLASS_NAME = ".";
private static final boolean DEBUG = false;
- private static class CacheEntry {
+ private static final int LOW_RES_SCALE_FACTOR = 8;
+
+ @Thunk static class CacheEntry {
public Bitmap icon;
public CharSequence title;
public CharSequence contentDescription;
+ public boolean isLowResIcon;
}
- private static class CacheKey {
- public ComponentName componentName;
- public UserHandleCompat user;
-
- CacheKey(ComponentName componentName, UserHandleCompat user) {
- this.componentName = componentName;
- this.user = user;
- }
+ private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons = new HashMap<>();
+ private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
- @Override
- public int hashCode() {
- return componentName.hashCode() + user.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- CacheKey other = (CacheKey) o;
- return other.componentName.equals(componentName) && other.user.equals(user);
- }
- }
-
- private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons =
- new HashMap<UserHandleCompat, Bitmap>();
private final Context mContext;
private final PackageManager mPackageManager;
private final UserManagerCompat mUserManager;
private final LauncherAppsCompat mLauncherApps;
- private final HashMap<CacheKey, CacheEntry> mCache =
- new HashMap<CacheKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
- private int mIconDpi;
+ private final HashMap<ComponentKey, CacheEntry> mCache =
+ new HashMap<ComponentKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+ private final int mIconDpi;
+ private final IconDB mIconDb;
+
+ private final Handler mWorkerHandler;
public IconCache(Context context) {
ActivityManager activityManager =
@@ -109,13 +99,12 @@ public class IconCache {
mUserManager = UserManagerCompat.getInstance(mContext);
mLauncherApps = LauncherAppsCompat.getInstance(mContext);
mIconDpi = activityManager.getLauncherLargeIconDensity();
+ mIconDb = new IconDB(context);
- // need to set mIconDpi before getting default icon
- UserHandleCompat myUser = UserHandleCompat.myUserHandle();
- mDefaultIcons.put(myUser, makeDefaultIcon(myUser));
+ mWorkerHandler = new Handler(LauncherModel.sWorkerThread.getLooper());
}
- public Drawable getFullResDefaultActivityIcon() {
+ private Drawable getFullResDefaultActivityIcon() {
return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon);
}
@@ -184,26 +173,171 @@ public class IconCache {
* Remove any records for the supplied ComponentName.
*/
public synchronized void remove(ComponentName componentName, UserHandleCompat user) {
- mCache.remove(new CacheKey(componentName, user));
+ mCache.remove(new ComponentKey(componentName, user));
}
/**
- * Remove any records for the supplied package name.
+ * Remove any records for the supplied package name from memory.
*/
- public synchronized void remove(String packageName, UserHandleCompat user) {
- HashSet<CacheKey> forDeletion = new HashSet<CacheKey>();
- for (CacheKey key: mCache.keySet()) {
+ private void removeFromMemCacheLocked(String packageName, UserHandleCompat user) {
+ HashSet<ComponentKey> forDeletion = new HashSet<ComponentKey>();
+ for (ComponentKey key: mCache.keySet()) {
if (key.componentName.getPackageName().equals(packageName)
&& key.user.equals(user)) {
forDeletion.add(key);
}
}
- for (CacheKey condemned: forDeletion) {
+ for (ComponentKey condemned: forDeletion) {
mCache.remove(condemned);
}
}
/**
+ * Updates the entries related to the given package in memory and persistent DB.
+ */
+ public synchronized void updateIconsForPkg(String packageName, UserHandleCompat user) {
+ removeIconsForPkg(packageName, user);
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
+ addIconToDBAndMemCache(app, info, userSerial);
+ }
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Package not found", e);
+ return;
+ }
+ }
+
+ /**
+ * Removes the entries related to the given package in memory and persistent DB.
+ */
+ public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
+ removeFromMemCacheLocked(packageName, user);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+ IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[] {packageName + "/%", Long.toString(userSerial)});
+ }
+
+ /**
+ * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
+ * the DB and are updated.
+ * @return The set of packages for which icons have updated.
+ */
+ public HashSet<String> updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps) {
+ mIconDb.updateSystemStateString(mContext);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ PackageManager pm = mContext.getPackageManager();
+ HashMap<String, PackageInfo> pkgInfoMap = new HashMap<String, PackageInfo>();
+ for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+ pkgInfoMap.put(info.packageName, info);
+ }
+
+ HashMap<ComponentName, LauncherActivityInfoCompat> componentMap = new HashMap<>();
+ for (LauncherActivityInfoCompat app : apps) {
+ componentMap.put(app.getComponentName(), app);
+ }
+
+ Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
+ new String[] {IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+ IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
+ IconDB.COLUMN_SYSTEM_STATE},
+ IconDB.COLUMN_USER + " = ? ",
+ new String[] {Long.toString(userSerial)},
+ null, null, null);
+
+ final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
+ final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
+ final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
+ final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
+ final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
+
+ HashSet<Integer> itemsToRemove = new HashSet<Integer>();
+ HashSet<String> updatedPackages = new HashSet<String>();
+
+ while (c.moveToNext()) {
+ String cn = c.getString(indexComponent);
+ ComponentName component = ComponentName.unflattenFromString(cn);
+ PackageInfo info = pkgInfoMap.get(component.getPackageName());
+ if (info == null) {
+ itemsToRemove.add(c.getInt(rowIndex));
+ continue;
+ }
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
+ // Application is not present
+ continue;
+ }
+
+ long updateTime = c.getLong(indexLastUpdate);
+ int version = c.getInt(indexVersion);
+ LauncherActivityInfoCompat app = componentMap.remove(component);
+ if (version == info.versionCode && updateTime == info.lastUpdateTime &&
+ TextUtils.equals(mIconDb.mSystemState, c.getString(systemStateIndex))) {
+ continue;
+ }
+ if (app == null) {
+ itemsToRemove.add(c.getInt(rowIndex));
+ continue;
+ }
+ ContentValues values = updateCacheAndGetContentValues(app);
+ mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
+ IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[] {cn, Long.toString(userSerial)});
+
+ updatedPackages.add(component.getPackageName());
+ }
+ c.close();
+ if (!itemsToRemove.isEmpty()) {
+ mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+ IconDB.COLUMN_ROWID + " IN ( " + TextUtils.join(", ", itemsToRemove) +" )",
+ null);
+ }
+
+ // Insert remaining apps.
+ for (LauncherActivityInfoCompat app : componentMap.values()) {
+ PackageInfo info = pkgInfoMap.get(app.getComponentName().getPackageName());
+ if (info == null) {
+ continue;
+ }
+ addIconToDBAndMemCache(app, info, userSerial);
+ }
+ return updatedPackages;
+ }
+
+ private void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
+ long userSerial) {
+ ContentValues values = updateCacheAndGetContentValues(app);
+ addIconToDB(values, app.getComponentName(), info, userSerial);
+ values.put(IconDB.COLUMN_COMPONENT, app.getComponentName().flattenToString());
+ }
+
+ /**
+ * Updates {@param values} to contain versoning information and adds it to the DB.
+ * @param values {@link ContentValues} containing icon & title
+ */
+ private void addIconToDB(ContentValues values, ComponentName key,
+ PackageInfo info, long userSerial) {
+ values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+ values.put(IconDB.COLUMN_VERSION, info.versionCode);
+ mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ private ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app) {
+ CacheEntry entry = new CacheEntry();
+ entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+ entry.title = app.getLabel();
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
+ mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
+
+ return mIconDb.newContentValues(entry.icon, entry.title.toString());
+ }
+
+ /**
* Empty out the cache.
*/
public synchronized void flush() {
@@ -214,7 +348,7 @@ public class IconCache {
* Empty out the cache that aren't of the correct grid size
*/
public synchronized void flushInvalidIcons(DeviceProfile grid) {
- Iterator<Entry<CacheKey, CacheEntry>> it = mCache.entrySet().iterator();
+ Iterator<Entry<ComponentKey, CacheEntry>> it = mCache.entrySet().iterator();
while (it.hasNext()) {
final CacheEntry e = it.next().getValue();
if ((e.icon != null) && (e.icon.getWidth() < grid.iconSizePx
@@ -225,18 +359,52 @@ public class IconCache {
}
/**
- * Fill in "application" with the icon and label for "info."
+ * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
+ * @return a request ID that can be used to cancel the request.
*/
- public synchronized void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info,
- HashMap<Object, CharSequence> labelCache) {
- CacheEntry entry = cacheLocked(application.componentName, info, labelCache,
- info.getUser(), false);
+ public IconLoadRequest updateIconInBackground(final BubbleTextView caller, final ItemInfo info) {
+ Runnable request = new Runnable() {
+
+ @Override
+ public void run() {
+ if (info instanceof AppInfo) {
+ getTitleAndIcon((AppInfo) info, null, false);
+ } else if (info instanceof ShortcutInfo) {
+ ShortcutInfo st = (ShortcutInfo) info;
+ getTitleAndIcon(st,
+ st.promisedIntent != null ? st.promisedIntent : st.intent,
+ st.user, false);
+ }
+ mMainThreadExecutor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ caller.reapplyItemInfo(info);
+ }
+ });
+ }
+ };
+ mWorkerHandler.post(request);
+ return new IconLoadRequest(request, mWorkerHandler);
+ }
+ /**
+ * Fill in "application" with the icon and label for "info."
+ */
+ public synchronized void getTitleAndIcon(AppInfo application,
+ LauncherActivityInfoCompat info, boolean useLowResIcon) {
+ CacheEntry entry = cacheLocked(application.componentName, info,
+ info == null ? application.user : info.getUser(),
+ false, useLowResIcon);
application.title = entry.title;
application.iconBitmap = entry.icon;
application.contentDescription = entry.contentDescription;
+ application.usingLowResIcon = entry.isLowResIcon;
}
+ /**
+ * Returns a high res icon for the given intent and user
+ */
public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) {
ComponentName component = intent.getComponent();
// null info means not installed, but if we have a component from the intent then
@@ -246,15 +414,16 @@ public class IconCache {
}
LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
- CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, true);
+ CacheEntry entry = cacheLocked(component, launcherActInfo, user, true, true);
return entry.icon;
}
/**
- * Fill in "shortcutInfo" with the icon and label for "info."
+ * Fill in {@param shortcutInfo} with the icon and label for {@param intent}. If the
+ * corresponding activity is not found, it reverts to the package icon.
*/
public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent,
- UserHandleCompat user, boolean usePkgIcon) {
+ UserHandleCompat user, boolean useLowResIcon) {
ComponentName component = intent.getComponent();
// null info means not installed, but if we have a component from the intent then
// we should still look in the cache for restored app icons.
@@ -262,17 +431,38 @@ public class IconCache {
shortcutInfo.setIcon(getDefaultIcon(user));
shortcutInfo.title = "";
shortcutInfo.usingFallbackIcon = true;
+ shortcutInfo.usingLowResIcon = false;
} else {
- LauncherActivityInfoCompat launcherActInfo =
- mLauncherApps.resolveActivity(intent, user);
- CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon);
-
- shortcutInfo.setIcon(entry.icon);
- shortcutInfo.title = entry.title;
- shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+ LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
+ getTitleAndIcon(shortcutInfo, component, info, user, true, useLowResIcon);
}
}
+ /**
+ * Fill in {@param shortcutInfo} with the icon and label for {@param info}
+ */
+ public synchronized void getTitleAndIcon(
+ ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
+ UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
+ CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
+ shortcutInfo.setIcon(entry.icon);
+ shortcutInfo.title = entry.title;
+ shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
+ shortcutInfo.usingLowResIcon = entry.isLowResIcon;
+ }
+
+ /**
+ * Fill in {@param appInfo} with the icon and label for {@param packageName}
+ */
+ public synchronized void getTitleAndIconForApp(
+ String packageName, UserHandleCompat user, boolean useLowResIcon,
+ PackageItemInfo infoOut) {
+ CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon);
+ infoOut.iconBitmap = entry.icon;
+ infoOut.title = entry.title;
+ infoOut.usingLowResIcon = entry.isLowResIcon;
+ infoOut.contentDescription = entry.contentDescription;
+ }
public synchronized Bitmap getDefaultIcon(UserHandleCompat user) {
if (!mDefaultIcons.containsKey(user)) {
@@ -281,16 +471,6 @@ public class IconCache {
return mDefaultIcons.get(user);
}
- public synchronized Bitmap getIcon(ComponentName component, LauncherActivityInfoCompat info,
- HashMap<Object, CharSequence> labelCache) {
- if (info == null || component == null) {
- return null;
- }
-
- CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser(), false);
- return entry.icon;
- }
-
public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
return mDefaultIcons.get(user) == icon;
}
@@ -300,44 +480,27 @@ public class IconCache {
* This method is not thread safe, it must be called from a synchronized method.
*/
private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
- HashMap<Object, CharSequence> labelCache, UserHandleCompat user, boolean usePackageIcon) {
- CacheKey cacheKey = new CacheKey(componentName, user);
+ UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
+ ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
- if (entry == null) {
+ if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
entry = new CacheEntry();
-
mCache.put(cacheKey, entry);
- if (info != null) {
- ComponentName labelKey = info.getComponentName();
- if (labelCache != null && labelCache.containsKey(labelKey)) {
- entry.title = labelCache.get(labelKey).toString();
- } else {
- entry.title = info.getLabel().toString();
- if (labelCache != null) {
- labelCache.put(labelKey, entry.title);
- }
- }
-
- entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
- entry.icon = Utilities.createIconBitmap(
- info.getBadgedIcon(mIconDpi), mContext);
- } else {
- entry.title = "";
- Bitmap preloaded = getPreloadedIcon(componentName, user);
- if (preloaded != null) {
- if (DEBUG) Log.d(TAG, "using preloaded icon for " +
- componentName.toShortString());
- entry.icon = preloaded;
+ // Check the DB first.
+ if (!getEntryFromDB(componentName, user, entry, useLowResIcon)) {
+ if (info != null) {
+ entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
} else {
if (usePackageIcon) {
- CacheEntry packageEntry = getEntryForPackage(
- componentName.getPackageName(), user);
+ CacheEntry packageEntry = getEntryForPackageLocked(
+ componentName.getPackageName(), user, false);
if (packageEntry != null) {
if (DEBUG) Log.d(TAG, "using package default icon for " +
componentName.toShortString());
entry.icon = packageEntry.icon;
entry.title = packageEntry.title;
+ entry.contentDescription = packageEntry.contentDescription;
}
}
if (entry.icon == null) {
@@ -347,6 +510,11 @@ public class IconCache {
}
}
}
+
+ if (TextUtils.isEmpty(entry.title) && info != null) {
+ entry.title = info.getLabel().toString();
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ }
}
return entry;
}
@@ -357,9 +525,9 @@ public class IconCache {
*/
public synchronized void cachePackageInstallInfo(String packageName, UserHandleCompat user,
Bitmap icon, CharSequence title) {
- remove(packageName, user);
+ removeFromMemCacheLocked(packageName, user);
- CacheEntry entry = getEntryForPackage(packageName, user);
+ CacheEntry entry = getEntryForPackageLocked(packageName, user, false);
if (!TextUtils.isEmpty(title)) {
entry.title = title;
}
@@ -371,56 +539,61 @@ public class IconCache {
/**
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
+ *
*/
- private CacheEntry getEntryForPackage(String packageName, UserHandleCompat user) {
- ComponentName cn = new ComponentName(packageName, EMPTY_CLASS_NAME);;
- CacheKey cacheKey = new CacheKey(cn, user);
+ private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
+ boolean useLowResIcon) {
+ ComponentName cn = new ComponentName(packageName, EMPTY_CLASS_NAME);
+ ComponentKey cacheKey = new ComponentKey(cn, user);
CacheEntry entry = mCache.get(cacheKey);
- if (entry == null) {
+ if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
entry = new CacheEntry();
- entry.title = "";
mCache.put(cacheKey, entry);
- try {
- ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0);
- entry.title = info.loadLabel(mPackageManager);
- entry.icon = Utilities.createIconBitmap(info.loadIcon(mPackageManager), mContext);
- } catch (NameNotFoundException e) {
- if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
- }
-
- if (entry.icon == null) {
- entry.icon = getPreloadedIcon(cn, user);
+ // Check the DB first.
+ if (!getEntryFromDB(cn, user, entry, useLowResIcon)) {
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
+ ApplicationInfo appInfo = info.applicationInfo;
+ if (appInfo == null) {
+ throw new NameNotFoundException("ApplicationInfo is null");
+ }
+ Drawable drawable = mUserManager.getBadgedDrawableForUser(
+ appInfo.loadIcon(mPackageManager), user);
+ entry.icon = Utilities.createIconBitmap(drawable, mContext);
+ entry.title = appInfo.loadLabel(mPackageManager);
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+ entry.isLowResIcon = false;
+
+ // Add the icon in the DB here, since these do not get written during
+ // package updates.
+ ContentValues values =
+ mIconDb.newContentValues(entry.icon, entry.title.toString());
+ addIconToDB(values, cn, info, mUserManager.getSerialNumberForUser(user));
+
+ } catch (NameNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+ }
}
}
return entry;
}
- public synchronized HashMap<ComponentName,Bitmap> getAllIcons() {
- HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
- for (CacheKey ck : mCache.keySet()) {
- final CacheEntry e = mCache.get(ck);
- set.put(ck.componentName, e.icon);
- }
- return set;
- }
-
/**
* Pre-load an icon into the persistent cache.
*
* <P>Queries for a component that does not exist in the package manager
* will be answered by the persistent cache.
*
- * @param context application context
* @param componentName the icon should be returned for this component
* @param icon the icon to be persisted
* @param dpi the native density of the icon
*/
- public static void preloadIcon(Context context, ComponentName componentName, Bitmap icon,
- int dpi) {
+ public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
+ long userSerial) {
// TODO rescale to the correct native DPI
try {
- PackageManager packageManager = context.getPackageManager();
+ PackageManager packageManager = mContext.getPackageManager();
packageManager.getActivityIcon(componentName);
// component is present on the system already, do nothing
return;
@@ -428,100 +601,135 @@ public class IconCache {
// pass
}
- final String key = componentName.flattenToString();
- FileOutputStream resourceFile = null;
+ ContentValues values = mIconDb.newContentValues(icon, label);
+ values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ private boolean getEntryFromDB(ComponentName component, UserHandleCompat user,
+ CacheEntry entry, boolean lowRes) {
+ Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
+ new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
+ IconDB.COLUMN_LABEL},
+ IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[] {component.flattenToString(),
+ Long.toString(mUserManager.getSerialNumberForUser(user))},
+ null, null, null);
try {
- resourceFile = context.openFileOutput(getResourceFilename(componentName),
- Context.MODE_PRIVATE);
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- if (icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 75, os)) {
- byte[] buffer = os.toByteArray();
- resourceFile.write(buffer, 0, buffer.length);
- } else {
- Log.w(TAG, "failed to encode cache for " + key);
- return;
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "failed to pre-load cache for " + key, e);
- } catch (IOException e) {
- Log.w(TAG, "failed to pre-load cache for " + key, e);
- } finally {
- if (resourceFile != null) {
- try {
- resourceFile.close();
- } catch (IOException e) {
- Log.d(TAG, "failed to save restored icon for: " + key, e);
+ if (c.moveToNext()) {
+ entry.icon = loadIconNoResize(c, 0);
+ entry.isLowResIcon = lowRes;
+ entry.title = c.getString(1);
+ if (entry.title == null) {
+ entry.title = "";
+ entry.contentDescription = "";
+ } else {
+ entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
}
+ return true;
}
+ } finally {
+ c.close();
}
+ return false;
}
- /**
- * Read a pre-loaded icon from the persistent icon cache.
- *
- * @param componentName the component that should own the icon
- * @returns a bitmap if one is cached, or null.
- */
- private Bitmap getPreloadedIcon(ComponentName componentName, UserHandleCompat user) {
- final String key = componentName.flattenToShortString();
+ public static class IconLoadRequest {
+ private final Runnable mRunnable;
+ private final Handler mHandler;
- // We don't keep icons for other profiles in persistent cache.
- if (!user.equals(UserHandleCompat.myUserHandle())) {
- return null;
+ IconLoadRequest(Runnable runnable, Handler handler) {
+ mRunnable = runnable;
+ mHandler = handler;
}
- if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
- Bitmap icon = null;
- FileInputStream resourceFile = null;
- try {
- resourceFile = mContext.openFileInput(getResourceFilename(componentName));
- byte[] buffer = new byte[1024];
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- int bytesRead = 0;
- while(bytesRead >= 0) {
- bytes.write(buffer, 0, bytesRead);
- bytesRead = resourceFile.read(buffer, 0, buffer.length);
- }
- if (DEBUG) Log.d(TAG, "read " + bytes.size());
- icon = BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size());
- if (icon == null) {
- Log.w(TAG, "failed to decode pre-load icon for " + key);
+ public void cancel() {
+ mHandler.removeCallbacks(mRunnable);
+ }
+ }
+
+ private static final class IconDB extends SQLiteOpenHelper {
+ private final static int DB_VERSION = 3;
+
+ private final static String TABLE_NAME = "icons";
+ private final static String COLUMN_ROWID = "rowid";
+ private final static String COLUMN_COMPONENT = "componentName";
+ private final static String COLUMN_USER = "profileId";
+ private final static String COLUMN_LAST_UPDATED = "lastUpdated";
+ private final static String COLUMN_VERSION = "version";
+ private final static String COLUMN_ICON = "icon";
+ private final static String COLUMN_ICON_LOW_RES = "icon_low_res";
+ private final static String COLUMN_LABEL = "label";
+ private final static String COLUMN_SYSTEM_STATE = "system_state";
+
+ public String mSystemState;
+
+ public IconDB(Context context) {
+ super(context, LauncherFiles.APP_ICONS_DB, null, DB_VERSION);
+ updateSystemStateString(context);
+ }
+
+ public void updateSystemStateString(Context c) {
+ mSystemState = Locale.getDefault().toString() + ","
+ + c.getResources().getConfiguration().mcc;
+ }
+
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+ COLUMN_COMPONENT + " TEXT NOT NULL, " +
+ COLUMN_USER + " INTEGER NOT NULL, " +
+ COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_ICON + " BLOB, " +
+ COLUMN_ICON_LOW_RES + " BLOB, " +
+ COLUMN_LABEL + " TEXT, " +
+ COLUMN_SYSTEM_STATE + " TEXT, " +
+ "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
}
- } catch (FileNotFoundException e) {
- if (DEBUG) Log.d(TAG, "there is no restored icon for: " + key);
- } catch (IOException e) {
- Log.w(TAG, "failed to read pre-load icon for: " + key, e);
- } finally {
- if(resourceFile != null) {
- try {
- resourceFile.close();
- } catch (IOException e) {
- Log.d(TAG, "failed to manage pre-load icon file: " + key, e);
- }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
}
}
- return icon;
- }
+ private void clearDB(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
- /**
- * Remove a pre-loaded icon from the persistent icon cache.
- *
- * @param componentName the component that should own the icon
- */
- public void deletePreloadedIcon(ComponentName componentName, UserHandleCompat user) {
- // We don't keep icons for other profiles in persistent cache.
- if (!user.equals(UserHandleCompat.myUserHandle()) || componentName == null) {
- return;
+ public ContentValues newContentValues(Bitmap icon, String label) {
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_ICON, Utilities.flattenBitmap(icon));
+ values.put(COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(
+ Bitmap.createScaledBitmap(icon,
+ icon.getWidth() / LOW_RES_SCALE_FACTOR,
+ icon.getHeight() / LOW_RES_SCALE_FACTOR, true)));
+ values.put(COLUMN_LABEL, label);
+ values.put(COLUMN_SYSTEM_STATE, mSystemState);
+ return values;
}
- remove(componentName, user);
- boolean success = mContext.deleteFile(getResourceFilename(componentName));
- if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache");
}
- private static String getResourceFilename(ComponentName component) {
- String resourceName = component.flattenToShortString();
- String filename = resourceName.replace(File.separatorChar, '_');
- return RESOURCE_FILE_PREFIX + filename;
+ private static Bitmap loadIconNoResize(Cursor c, int iconIndex) {
+ byte[] data = c.getBlob(iconIndex);
+ try {
+ return BitmapFactory.decodeByteArray(data, 0, data.length);
+ } catch (Exception e) {
+ return null;
+ }
}
}
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 3c36361aa..e48640c93 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -18,21 +18,14 @@ package com.android.launcher3;
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.provider.Settings;
import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
+import com.android.launcher3.R;
import com.android.launcher3.compat.UserHandleCompat;
public class InfoDropTarget extends ButtonDropTarget {
- private ColorStateList mOriginalTextColor;
- private TransitionDrawable mDrawable;
-
public InfoDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -44,43 +37,10 @@ public class InfoDropTarget extends ButtonDropTarget {
@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();
-
- if (mDrawable == null) {
- // TODO: investigate why this is ever happening. Presently only on one known device.
- mDrawable = (TransitionDrawable) r.getDrawable(R.drawable.info_target_selector);
- setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
- }
-
- if (null != mDrawable) {
- mDrawable.setCrossFadeEnabled(true);
- }
-
- // Remove the text in the Phone UI in landscape
- int orientation = getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- if (!LauncherAppState.getInstance().isScreenLarge()) {
- setText("");
- }
- }
- }
-
- @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.
+ mHoverColor = getResources().getColor(R.color.info_target_hover_tint);
- startDetailsActivityForInfo(d.dragInfo, mLauncher);
- // There is no post-drop animation, so clean up the DragView now
- d.deferDragViewCleanupPostAnimation = false;
- return false;
+ setDrawable(R.drawable.info_target_selector);
}
public static void startDetailsActivityForInfo(Object info, Launcher launcher) {
@@ -105,39 +65,14 @@ public class InfoDropTarget extends ButtonDropTarget {
}
@Override
- public void onDragStart(DragSource source, Object info, int dragAction) {
- boolean isVisible = true;
-
- // Hide this button unless we are dragging something from AllApps
- if (!source.supportsAppInfoDropTarget()) {
- isVisible = false;
- }
-
- mActive = isVisible;
- mDrawable.resetTransition();
- setTextColor(mOriginalTextColor);
- ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ protected boolean supportsDrop(DragSource source, Object info) {
+ return source.supportsAppInfoDropTarget() &&
+ Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
}
@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);
- }
+ void completeDrop(DragObject d) {
+ startDetailsActivityForInfo(d.dragInfo, mLauncher);
}
}
diff --git a/src/com/android/launcher3/Insettable.java b/src/com/android/launcher3/Insettable.java
index 1d2356c65..3b8ef2f93 100644
--- a/src/com/android/launcher3/Insettable.java
+++ b/src/com/android/launcher3/Insettable.java
@@ -18,6 +18,10 @@ package com.android.launcher3;
import android.graphics.Rect;
+/**
+ * Allows the implementing {@link View} to not draw underneath system bars.
+ * e.g., notification bar on top and home key area on the bottom.
+ */
public interface Insettable {
void setInsets(Rect insets);
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 1ab308558..0c69154aa 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
@@ -32,6 +33,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.Thunk;
import org.json.JSONException;
import org.json.JSONObject;
@@ -146,6 +148,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+ info = convertToLauncherActivityIfPossible(info);
queuePendingShortcutInfo(info, context);
}
@@ -186,11 +189,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
final PendingInstallShortcutInfo pendingInfo = iter.next();
final Intent intent = pendingInfo.launchIntent;
- if (LauncherAppState.isDisableAllApps() && !isValidShortcutLaunchIntent(intent)) {
- if (DBG) Log.d(TAG, "Ignoring shortcut with launchIntent:" + intent);
- continue;
- }
-
// If the intent specifies a package, make sure the package exists
String packageName = pendingInfo.getTargetPackage();
if (!TextUtils.isEmpty(packageName)) {
@@ -212,7 +210,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
// Add the new apps to the model and bind them
if (!addShortcuts.isEmpty()) {
LauncherAppState app = LauncherAppState.getInstance();
- app.getModel().addAndBindAddedWorkspaceApps(context, addShortcuts);
+ app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
}
}
}
@@ -245,7 +243,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
* Ensures that we have a valid, non-null name. If the provided name is null, we will return
* the application name instead.
*/
- private static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
+ @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
if (name == null) {
try {
PackageManager pm = context.getPackageManager();
@@ -335,7 +333,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(NAME_KEY).value(name);
if (icon != null) {
- byte[] iconByteArray = ItemInfo.flattenBitmap(icon);
+ byte[] iconByteArray = Utilities.flattenBitmap(icon);
json = json.key(ICON_KEY).value(
Base64.encodeToString(
iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
@@ -354,16 +352,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
public ShortcutInfo getShortcutInfo() {
if (activityInfo != null) {
- final ShortcutInfo info = new ShortcutInfo();
- info.user = user;
- info.title = label;
- info.contentDescription = label;
- info.customIcon = false;
- info.intent = launchIntent;
- info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- info.flags = AppInfo.initFlags(activityInfo);
- info.firstInstallTime = activityInfo.getFirstInstallTime();
- return info;
+ return ShortcutInfo.fromActivityInfo(activityInfo, mContext);
} else {
return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
}
@@ -377,6 +366,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
}
return packageName;
}
+
+ public boolean isLuncherActivity() {
+ return activityInfo != null;
+ }
}
private static PendingInstallShortcutInfo decode(String encoded, Context context) {
@@ -424,4 +417,40 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
}
return null;
}
+
+ /**
+ * Tries to create a new PendingInstallShortcutInfo which represents the same target,
+ * but is an app target and not a shortcut.
+ * @return the newly created info or the original one.
+ */
+ private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
+ PendingInstallShortcutInfo original) {
+ if (original.isLuncherActivity()) {
+ // Already an activity target
+ return original;
+ }
+ if (isValidShortcutLaunchIntent(original.launchIntent)
+ || !original.user.equals(UserHandleCompat.myUserHandle())) {
+ // We can only convert shortcuts which point to a main activity in the current user.
+ return original;
+ }
+
+ PackageManager pm = original.mContext.getPackageManager();
+ ResolveInfo info = pm.resolveActivity(original.launchIntent, 0);
+
+ if (info == null) {
+ return original;
+ }
+
+ // Ignore any conflicts in the label name, as that can change based on locale.
+ LauncherActivityInfoCompat launcherInfo = LauncherActivityInfoCompat
+ .fromResolveInfo(info, original.mContext);
+ return new PendingInstallShortcutInfo(launcherInfo, original.mContext);
+ }
+
+ public static boolean isLauncherActivity(Intent intent, Context context) {
+ Intent data = new Intent().putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
+ PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+ return convertToLauncherActivityIfPossible(info).isLuncherActivity();
+ }
}
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
index 2898b347d..29df38bae 100644
--- a/src/com/android/launcher3/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -21,6 +21,8 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.view.View;
+import com.android.launcher3.util.Thunk;
+
/**
* 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
@@ -43,7 +45,7 @@ public class InterruptibleInOutAnimator {
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;
+ @Thunk int mDirection = STOPPED;
public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
mAnimator = LauncherAnimUtils.ofFloat(view, fromValue, toValue).setDuration(duration);
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index aff8323c2..f7e0ea488 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -20,13 +20,10 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.util.Log;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.util.Arrays;
/**
@@ -39,7 +36,7 @@ public class ItemInfo {
*/
static final String EXTRA_PROFILE = "profile";
- static final int NO_ID = -1;
+ public static final int NO_ID = -1;
/**
* The id in the settings database for this item
@@ -85,7 +82,7 @@ public class ItemInfo {
/**
* Indicates the Y cell span.
*/
- int spanY = 1;
+ public int spanY = 1;
/**
* Indicates the minimum X cell span.
@@ -110,21 +107,21 @@ public class ItemInfo {
/**
* Title of the item
*/
- CharSequence title;
+ public CharSequence title;
/**
* Content description of the item.
*/
- CharSequence contentDescription;
+ public CharSequence contentDescription;
/**
* The position of the item in a drag-and-drop operation.
*/
- int[] dropPos = null;
+ public int[] dropPos = null;
- UserHandleCompat user;
+ public UserHandleCompat user;
- ItemInfo() {
+ public ItemInfo() {
user = UserHandleCompat.myUserHandle();
}
@@ -177,25 +174,9 @@ public class ItemInfo {
}
}
- 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);
+ byte[] data = Utilities.flattenBitmap(bitmap);
values.put(LauncherSettings.Favorites.ICON, data);
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5b8eaef50..2bfb29ef6 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1,4 +1,3 @@
-
/*
* Copyright (C) 2008 The Android Open Source Project
*
@@ -22,7 +21,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
@@ -49,7 +47,6 @@ import android.content.pm.ApplicationInfo;
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.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
@@ -83,19 +80,17 @@ import android.view.Surface;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
-import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Advanceable;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
@@ -107,6 +102,9 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetsContainerView;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -116,7 +114,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DateFormat;
@@ -133,12 +130,13 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class Launcher extends Activity
implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
- View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
+ View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
+ LauncherStateTransitionAnimation.Callbacks {
static final String TAG = "Launcher";
- static final boolean LOGD = false;
+ static final boolean LOGD = true;
static final boolean PROFILE_STARTUP = false;
- static final boolean DEBUG_WIDGETS = false;
+ static final boolean DEBUG_WIDGETS = true;
static final boolean DEBUG_STRICT_MODE = false;
static final boolean DEBUG_RESUME_TIME = false;
static final boolean DEBUG_DUMP_LOG = false;
@@ -147,7 +145,6 @@ public class Launcher extends Activity
private static final int REQUEST_CREATE_SHORTCUT = 1;
private static final int REQUEST_CREATE_APPWIDGET = 5;
- private static final int REQUEST_PICK_SHORTCUT = 7;
private static final int REQUEST_PICK_APPWIDGET = 9;
private static final int REQUEST_PICK_WALLPAPER = 10;
@@ -167,7 +164,6 @@ public class Launcher extends Activity
// To turn on these properties, type
// adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
- static final String DISABLE_ALL_APPS_PROPERTY = "launcher_noallapps";
// The Intent extra that defines whether to ignore the launch animation
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
@@ -216,9 +212,10 @@ public class Launcher extends Activity
public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
/** The different states that Launcher can be in. */
- private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
- private State mState = State.WORKSPACE;
- private AnimatorSet mStateAnimation;
+ enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED };
+ @Thunk State mState = State.WORKSPACE;
+ @Thunk AnimatorSet mStateAnimation;
+ @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
private boolean mIsSafeModeEnabled;
@@ -231,16 +228,13 @@ public class Launcher extends Activity
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final int ACTIVITY_START_DELAY = 1000;
- private static final Object sLock = new Object();
-
private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
// How long to wait before the new-shortcut animation automatically pans the workspace
private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
- private static int NEW_APPS_ANIMATION_DELAY = 500;
- private static final int SINGLE_FRAME_DELAY = 16;
+ @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
private final BroadcastReceiver mCloseSystemDialogsReceiver
= new CloseSystemDialogsIntentReceiver();
@@ -248,17 +242,17 @@ public class Launcher extends Activity
private LayoutInflater mInflater;
- private Workspace mWorkspace;
+ @Thunk Workspace mWorkspace;
private View mLauncherView;
private View mPageIndicators;
- private DragLayer mDragLayer;
+ @Thunk DragLayer mDragLayer;
private DragController mDragController;
private View mWeightWatcher;
private AppWidgetManagerCompat mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
- private ItemInfo mPendingAddInfo = new ItemInfo();
+ @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
private int mPendingAddWidgetId = -1;
@@ -272,8 +266,13 @@ public class Launcher extends Activity
private View mAllAppsButton;
private SearchDropTargetBar mSearchDropTargetBar;
- private AppsCustomizeTabHost mAppsCustomizeTabHost;
- private AppsCustomizePagedView mAppsCustomizeContent;
+
+ // Main container view for the all apps screen.
+ @Thunk AppsContainerView mAppsView;
+
+ // Main container view for the widget tray screen.
+ private WidgetsContainerView mWidgetsView;
+
private boolean mAutoAdvanceRunning = false;
private AppWidgetHostView mQsb;
@@ -285,7 +284,7 @@ public class Launcher extends Activity
private SpannableStringBuilder mDefaultKeySsb = null;
- private boolean mWorkspaceLoading = true;
+ @Thunk boolean mWorkspaceLoading = true;
private boolean mPaused = true;
private boolean mRestoring;
@@ -299,34 +298,31 @@ public class Launcher extends Activity
private LauncherModel mModel;
private IconCache mIconCache;
- private boolean mUserPresent = true;
+ @Thunk boolean mUserPresent = true;
private boolean mVisible = false;
private boolean mHasFocus = false;
private boolean mAttached = false;
- private static LocaleConfiguration sLocaleConfiguration = null;
+ @Thunk static LocaleConfiguration sLocaleConfiguration = null;
private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
private View.OnTouchListener mHapticFeedbackTouchListener;
- public static final int BUILD_LAYER = 0;
- public static final int BUILD_AND_SET_LAYER = 1;
-
// 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<View, AppWidgetProviderInfo> mWidgetsToAdvance =
+ @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
new HashMap<View, AppWidgetProviderInfo>();
// Determines how long to wait after a rotation before restoring the screen orientation to
// match the sensor state.
private final int mRestoreScreenOrientationDelay = 500;
- private Drawable mWorkspaceBackgroundDrawable;
+ @Thunk Drawable mWorkspaceBackgroundDrawable;
private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
@@ -342,11 +338,9 @@ public class Launcher extends Activity
// it from the context.
private SharedPreferences mSharedPrefs;
- private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null;
-
// 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 ImageView mFolderIconImageView;
+ @Thunk ImageView mFolderIconImageView;
private Bitmap mFolderIconBitmap;
private Canvas mFolderIconCanvas;
private Rect mRectForFolderAnimation = new Rect();
@@ -363,7 +357,19 @@ public class Launcher extends Activity
}
}
- private Runnable mBuildLayersRunnable = new Runnable() {
+ // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
+ private static Method sClipRevealMethod = null;
+ static {
+ Class<?> activityOptionsClass = ActivityOptions.class;
+ try {
+ sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
+ View.class, int.class, int.class, int.class, int.class);
+ } catch (Exception e) {
+ // Earlier version
+ }
+ }
+
+ @Thunk Runnable mBuildLayersRunnable = new Runnable() {
public void run() {
if (mWorkspace != null) {
mWorkspace.buildPageHardwareLayers();
@@ -373,7 +379,7 @@ public class Launcher extends Activity
private static PendingAddArguments sPendingAddItem;
- private static class PendingAddArguments {
+ @Thunk static class PendingAddArguments {
int requestCode;
Intent intent;
long container;
@@ -426,6 +432,7 @@ public class Launcher extends Activity
mIconCache.flushInvalidIcons(grid);
mDragController = new DragController(this);
mInflater = getLayoutInflater();
+ mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
mStats = new Stats(this);
@@ -513,6 +520,17 @@ public class Launcher extends Activity
public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
mLauncherCallbacks = callbacks;
+ mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
+ @Override
+ public void onAllAppsBoundsChanged(Rect bounds) {
+ mAppsView.setFixedBounds(Launcher.this, bounds);
+ }
+
+ @Override
+ public void dismissAllApps() {
+ showWorkspace(true);
+ }
+ });
return true;
}
@@ -561,7 +579,7 @@ public class Launcher extends Activity
}
}
- private void checkForLocaleChange() {
+ @Thunk void checkForLocaleChange() {
if (sLocaleConfiguration == null) {
new AsyncTask<Void, Void, LocaleConfiguration>() {
@Override
@@ -610,13 +628,13 @@ public class Launcher extends Activity
}
}
- private static class LocaleConfiguration {
+ @Thunk static class LocaleConfiguration {
public String locale;
public int mcc = -1;
public int mnc = -1;
}
- private static void readConfiguration(Context context, LocaleConfiguration configuration) {
+ @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) {
DataInputStream in = null;
try {
in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
@@ -638,7 +656,7 @@ public class Launcher extends Activity
}
}
- private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
+ @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) {
DataOutputStream out = null;
try {
out = new DataOutputStream(context.openFileOutput(
@@ -671,7 +689,7 @@ public class Launcher extends Activity
return mInflater;
}
- boolean isDraggingEnabled() {
+ public 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();
@@ -915,7 +933,7 @@ public class Launcher extends Activity
}
}
- private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
+ @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
CellLayout cellLayout =
(CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
Runnable onCompleteRunnable = null;
@@ -987,8 +1005,10 @@ public class Launcher extends Activity
// Restore the previous launcher state
if (mOnResumeState == State.WORKSPACE) {
showWorkspace(false);
- } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
- showAllApps(false, mAppsCustomizeContent.getContentType(), false);
+ } else if (mOnResumeState == State.APPS) {
+ showAppsView(false /* animated */, false /* resetListToTop */);
+ } else if (mOnResumeState == State.WIDGETS) {
+ showWidgetsView(false, false);
}
mOnResumeState = State.NONE;
@@ -1010,15 +1030,9 @@ public class Launcher extends Activity
startTimeCallbacks = System.currentTimeMillis();
}
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.setBulkBind(true);
- }
for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
mBindOnResumeCallbacks.get(i).run();
}
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.setBulkBind(false);
- }
mBindOnResumeCallbacks.clear();
if (DEBUG_RESUME_TIME) {
Log.d(TAG, "Time spent processing callbacks in onResume: " +
@@ -1044,6 +1058,7 @@ public class Launcher extends Activity
// (framework issue). On resuming, we ensure that any widgets are inflated for the current
// orientation.
getWorkspace().reinflateWidgetsIfNecessary();
+ reinflateQSBIfNecessary();
// Process any items that were added while Launcher was away.
InstallShortcutReceiver.disableAndFlushInstallQueue(this);
@@ -1137,6 +1152,19 @@ public class Launcher extends Activity
public void forceExitFullImmersion();
}
+ public interface LauncherAppsCallbacks {
+ /**
+ * Updates launcher to the available space that AllApps can take so as not to overlap with
+ * any other views.
+ */
+ public void onAllAppsBoundsChanged(Rect bounds);
+
+ /**
+ * Called to dismiss all apps if it is showing.
+ */
+ public void dismissAllApps();
+ }
+
public interface LauncherOverlayCallbacks {
/**
* This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
@@ -1209,9 +1237,8 @@ public class Launcher extends Activity
if (mModel.isCurrentCallbacks(this)) {
mModel.stopLoader();
}
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.surrender();
- }
+ //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
+
return Boolean.TRUE;
}
@@ -1297,8 +1324,8 @@ public class Launcher extends Activity
}
State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
- if (state == State.APPS_CUSTOMIZE) {
- mOnResumeState = State.APPS_CUSTOMIZE;
+ if (state == State.APPS || state == State.WIDGETS) {
+ mOnResumeState = state;
}
int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
@@ -1332,19 +1359,6 @@ public class Launcher extends Activity
mRestoring = true;
}
- // Restore the AppsCustomize tab
- if (mAppsCustomizeTabHost != null) {
- String curTab = savedState.getString("apps_customize_currentTab");
- if (curTab != null) {
- mAppsCustomizeTabHost.setContentTypeImmediate(
- mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
- mAppsCustomizeContent.loadAssociatedPages(
- mAppsCustomizeContent.getCurrentPage());
- }
-
- int currentIndex = savedState.getInt("apps_customize_currentIndex");
- mAppsCustomizeContent.restorePageForIndex(currentIndex);
- }
mItemIdToViewId = (HashMap<Integer, Integer>)
savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
}
@@ -1426,11 +1440,14 @@ public class Launcher extends Activity
mSearchDropTargetBar = (SearchDropTargetBar)
mDragLayer.findViewById(R.id.search_drop_target_bar);
+ // Setup Apps
+ mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
+ if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) {
+ mAppsView.hideSearchBar();
+ }
+
// Setup AppsCustomize
- mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
- mAppsCustomizeContent = (AppsCustomizePagedView)
- mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
- mAppsCustomizeContent.setup(this, dragController);
+ mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
// Setup the drag controller (drop targets have to be added in reverse order in priority)
dragController.setDragScoller(mWorkspace);
@@ -1439,7 +1456,7 @@ public class Launcher extends Activity
dragController.addDropTarget(mWorkspace);
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.setup(this, dragController);
- mSearchDropTargetBar.setQsbSearchBar(getQsbBar());
+ mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
}
if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
@@ -1548,7 +1565,7 @@ public class Launcher extends Activity
return;
}
- LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
+ LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
if (!mRestoring) {
mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
@@ -1588,7 +1605,7 @@ public class Launcher extends Activity
*
* @param appWidgetId The app widget id
*/
- private void completeAddAppWidget(int appWidgetId, long container, long screenId,
+ @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
ItemInfo info = mPendingAddInfo;
@@ -1610,7 +1627,7 @@ public class Launcher extends Activity
launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
LauncherModel.addItemToDatabase(this, launcherInfo,
- container, screenId, info.cellX, info.cellY, false);
+ container, screenId, info.cellX, info.cellY);
if (!mRestoring) {
if (hostView == null) {
@@ -1640,16 +1657,17 @@ public class Launcher extends Activity
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mUserPresent = false;
mDragLayer.clearAllResizeFrames();
- updateRunning();
+ updateAutoAdvanceState();
// 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) {
+ if (mAppsView != null && mWidgetsView != null &&
+ mPendingAddInfo.container == ItemInfo.NO_ID) {
showWorkspace(false);
}
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
mUserPresent = true;
- updateRunning();
+ updateAutoAdvanceState();
} else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
mModel.resetLoadedState(false, true);
mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
@@ -1692,40 +1710,19 @@ public class Launcher extends Activity
* Sets up transparent navigation and status bars in LMP.
* This method is a no-op for other platform versions.
*/
- @TargetApi(19)
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setupTransparentSystemBarsForLmp() {
- // TODO(sansid): use the APIs directly when compiling against L sdk.
- // Currently we use reflection to access the flags and the API to set the transparency
- // on the System bars.
if (Utilities.isLmpOrAbove()) {
- try {
- getWindow().getAttributes().systemUiVisibility |=
- (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
- | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField(
- "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS");
- getWindow().addFlags(drawsSysBackgroundsField.getInt(null));
-
- Method setStatusBarColorMethod =
- Window.class.getDeclaredMethod("setStatusBarColor", int.class);
- Method setNavigationBarColorMethod =
- Window.class.getDeclaredMethod("setNavigationBarColor", int.class);
- setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
- setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
- } catch (NoSuchFieldException e) {
- Log.w(TAG, "NoSuchFieldException while setting up transparent bars");
- } catch (NoSuchMethodException ex) {
- Log.w(TAG, "NoSuchMethodException while setting up transparent bars");
- } catch (IllegalAccessException e) {
- Log.w(TAG, "IllegalAccessException while setting up transparent bars");
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "IllegalArgumentException while setting up transparent bars");
- } catch (InvocationTargetException e) {
- Log.w(TAG, "InvocationTargetException while setting up transparent bars");
- } finally {}
+ Window window = getWindow();
+ window.getAttributes().systemUiVisibility |=
+ (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.setStatusBarColor(Color.TRANSPARENT);
+ window.setNavigationBarColor(Color.TRANSPARENT);
}
}
@@ -1738,17 +1735,16 @@ public class Launcher extends Activity
unregisterReceiver(mReceiver);
mAttached = false;
}
- updateRunning();
+ updateAutoAdvanceState();
}
public void onWindowVisibilityChanged(int visibility) {
mVisible = visibility == View.VISIBLE;
- updateRunning();
+ updateAutoAdvanceState();
// 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
@@ -1783,14 +1779,14 @@ public class Launcher extends Activity
}
}
- private void sendAdvanceMessage(long delay) {
+ @Thunk void sendAdvanceMessage(long delay) {
mHandler.removeMessages(ADVANCE_MSG);
Message msg = mHandler.obtainMessage(ADVANCE_MSG);
mHandler.sendMessageDelayed(msg, delay);
mAutoAdvanceSentTime = System.currentTimeMillis();
}
- private void updateRunning() {
+ @Thunk void updateAutoAdvanceState() {
boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
if (autoAdvanceRunning != mAutoAdvanceRunning) {
mAutoAdvanceRunning = autoAdvanceRunning;
@@ -1836,14 +1832,14 @@ public class Launcher extends Activity
if (v instanceof Advanceable) {
mWidgetsToAdvance.put(hostView, appWidgetInfo);
((Advanceable) v).fyiWillBeAdvancedByHostKThx();
- updateRunning();
+ updateAutoAdvanceState();
}
}
void removeWidgetToAutoAdvance(View hostView) {
if (mWidgetsToAdvance.containsKey(hostView)) {
mWidgetsToAdvance.remove(hostView);
- updateRunning();
+ updateAutoAdvanceState();
}
}
@@ -1852,19 +1848,23 @@ public class Launcher extends Activity
launcherInfo.hostView = null;
}
- void showOutOfSpaceMessage(boolean isHotseatLayout) {
+ public 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 ArrayList<AppInfo> getAllAppsList() {
- return mAppsCustomizeContent.getApps();
- }
-
public DragLayer getDragLayer() {
return mDragLayer;
}
+ public AppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
+ public WidgetsContainerView getWidgetsView() {
+ return mWidgetsView;
+ }
+
public Workspace getWorkspace() {
return mWorkspace;
}
@@ -1950,9 +1950,14 @@ public class Launcher extends Activity
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
- // Reset the apps customize page
- if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
- mAppsCustomizeTabHost.reset();
+ // Reset the apps view
+ if (!alreadyOnHome && mAppsView != null) {
+ mAppsView.scrollToTop();
+ }
+
+ // Reset the widgets view
+ if (!alreadyOnHome && mWidgetsView != null) {
+ mWidgetsView.scrollToTop();
}
if (mLauncherCallbacks != null) {
@@ -2007,16 +2012,8 @@ public class Launcher extends Activity
outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
}
- // Save the current AppsCustomize tab
- if (mAppsCustomizeTabHost != null) {
- AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
- String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
- if (currentTabTag != null) {
- outState.putString("apps_customize_currentTab", currentTabTag);
- }
- int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
- outState.putInt("apps_customize_currentIndex", currentIndex);
- }
+ // Save the current widgets tray?
+ // TODO(hyunyoungs)
outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
if (mLauncherCallbacks != null) {
@@ -2414,8 +2411,8 @@ public class Launcher extends Activity
folderInfo.title = getText(R.string.folder_name);
// Update the model
- LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
- false);
+ LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
+ cellX, cellY);
sFolders.put(folderInfo.id, folderInfo);
// Create the view
@@ -2433,13 +2430,6 @@ public class Launcher extends Activity
sFolders.remove(folder.id);
}
- protected ComponentName getWallpaperPickerComponent() {
- if (mLauncherCallbacks != null) {
- return mLauncherCallbacks.getWallpaperPickerComponent();
- }
- return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
- }
-
/**
* Registers various content observers. The current implementation registers
* only a favorites observer to keep track of the favorites applications.
@@ -2479,13 +2469,16 @@ public class Launcher extends Activity
return;
}
- if (isAllAppsVisible()) {
- if (mAppsCustomizeContent.getContentType() ==
- AppsCustomizePagedView.ContentType.Applications) {
- showWorkspace(true);
- } else {
- showOverviewMode(true);
- }
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate != null && delegate.onBackPressed()) {
+ return;
+ }
+
+ if (isAppsViewVisible()) {
+ showWorkspace(true);
+ } else if (isWidgetsViewVisible()) {
+ showOverviewMode(true);
} else if (mWorkspace.isInOverviewMode()) {
mWorkspace.exitOverviewMode(true);
} else if (mWorkspace.getOpenFolder() != null) {
@@ -2506,7 +2499,7 @@ public class Launcher extends Activity
/**
* Re-listen when widgets are reset.
*/
- private void onAppWidgetReset() {
+ @Thunk void onAppWidgetReset() {
if (mAppWidgetHost != null) {
mAppWidgetHost.startListening();
}
@@ -2616,13 +2609,10 @@ public class Launcher extends Activity
*/
protected void onClickAllAppsButton(View v) {
if (LOGD) Log.d(TAG, "onClickAllAppsButton");
- if (isAllAppsVisible()) {
+ if (isAppsViewVisible()) {
showWorkspace(true);
} else {
- showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false);
- }
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onClickAllAppsButton(v);
+ showAppsView(true /* animated */, false /* resetListToTop */);
}
}
@@ -2704,7 +2694,7 @@ public class Launcher extends Activity
}
}
- private void startAppShortcutOrInfoActivity(View v) {
+ @Thunk void startAppShortcutOrInfoActivity(View v) {
Object tag = v.getTag();
final ShortcutInfo shortcut;
final Intent intent;
@@ -2790,7 +2780,7 @@ public class Launcher extends Activity
if (mIsSafeModeEnabled) {
Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
} else {
- showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+ showWidgetsView(true /* animated */, true /* resetPageToZero */);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onClickAddWidgetButton(view);
}
@@ -2803,9 +2793,8 @@ public class Launcher extends Activity
*/
protected void onClickWallpaperPicker(View v) {
if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
- final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
- pickWallpaper.setComponent(getWallpaperPickerComponent());
- startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
+ startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
+ REQUEST_PICK_WALLPAPER);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onClickWallpaperPicker(v);
@@ -2939,12 +2928,39 @@ public class Launcher extends Activity
}
Bundle optsBundle = null;
- if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
- // On pre-L devices, we use the scale up transition.
- // Otherwise we use system default.
- ActivityOptions opts =
- ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
- optsBundle = opts.toBundle();
+ if (useLaunchAnimation) {
+ ActivityOptions opts = null;
+ if (sClipRevealMethod != null) {
+ // TODO: call method directly when Launcher3 can depend on M APIs
+ int left = 0, top = 0;
+ int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
+ if (v instanceof TextView) {
+ // Launch from center of icon, not entire view
+ Drawable icon = Workspace.getTextViewIcon((TextView) v);
+ if (icon != null) {
+ Rect bounds = icon.getBounds();
+ left = (width - bounds.width()) / 2;
+ top = v.getPaddingTop();
+ width = bounds.width();
+ height = bounds.height();
+ }
+ }
+ try {
+ opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
+ left, top, width, height);
+ } catch (IllegalAccessException e) {
+ Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
+ sClipRevealMethod = null;
+ } catch (InvocationTargetException e) {
+ Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
+ sClipRevealMethod = null;
+ }
+ }
+ if (opts == null && !Utilities.isLmpOrAbove()) {
+ opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
+ v.getMeasuredWidth(), v.getMeasuredHeight());
+ }
+ optsBundle = opts != null ? opts.toBundle() : null;
}
if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
@@ -3096,10 +3112,19 @@ public class Launcher extends Activity
*/
public void openFolder(FolderIcon folderIcon) {
Folder folder = folderIcon.getFolder();
+ Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
+ if (openFolder != null && openFolder != folder) {
+ // Close any open folder before opening a folder.
+ closeFolder();
+ }
+
FolderInfo info = folder.mInfo;
info.opened = true;
+ // While the folder is open, the position of the icon cannot change.
+ ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
+
// 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) {
@@ -3135,6 +3160,9 @@ public class Launcher extends Activity
if (parent != null) {
FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
shrinkAndFadeInFolderIcon(fi);
+ if (fi != null) {
+ ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
+ }
}
folder.animateClosed();
@@ -3166,7 +3194,7 @@ public class Launcher extends Activity
View itemUnderLongClick = null;
if (v.getTag() instanceof ItemInfo) {
ItemInfo info = (ItemInfo) v.getTag();
- longClickCellInfo = new CellLayout.CellInfo(v, info);;
+ longClickCellInfo = new CellLayout.CellInfo(v, info);
itemUnderLongClick = longClickCellInfo.cell;
resetAddInfo();
}
@@ -3207,7 +3235,7 @@ public class Launcher extends Activity
/**
* Returns the CellLayout of the specified container at the specified screen.
*/
- CellLayout getCellLayout(long container, long screenId) {
+ public CellLayout getCellLayout(long container, long screenId) {
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
if (mHotseat != null) {
return mHotseat.getLayout();
@@ -3215,12 +3243,23 @@ public class Launcher extends Activity
return null;
}
} else {
- return (CellLayout) mWorkspace.getScreenWithId(screenId);
+ return mWorkspace.getScreenWithId(screenId);
}
}
+ /**
+ * For overridden classes.
+ */
public boolean isAllAppsVisible() {
- return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
+ return isAppsViewVisible();
+ }
+
+ public boolean isAppsViewVisible() {
+ return (mState == State.APPS) || (mOnResumeState == State.APPS);
+ }
+
+ public boolean isWidgetsViewVisible() {
+ return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
}
private void setWorkspaceBackground(boolean workspace) {
@@ -3238,579 +3277,6 @@ public class Launcher extends Activity
setWorkspaceBackground(visible);
}
- 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) {
- AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
- showAppsCustomizeHelper(animated, springLoaded, contentType);
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
- final AppsCustomizePagedView.ContentType contentType) {
- if (mStateAnimation != null) {
- mStateAnimation.setDuration(0);
- mStateAnimation.cancel();
- mStateAnimation = null;
- }
-
- boolean material = Utilities.isLmpOrAbove();
-
- final Resources res = getResources();
-
- final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
- final int itemsAlphaStagger =
- res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
-
- final View fromView = mWorkspace;
- final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
-
- final HashMap<View, Integer> layerViews = new HashMap<View, Integer>();
-
- Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ?
- Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN;
- Animator workspaceAnim =
- mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews);
- if (!LauncherAppState.isDisableAllApps()
- || contentType == AppsCustomizePagedView.ContentType.Widgets) {
- // Set the content type for the all apps/widgets space
- mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
- }
-
- // If for some reason our views aren't initialized, don't animate
- boolean initialized = getAllAppsButton() != null;
-
- if (animated && initialized) {
- mStateAnimation = LauncherAnimUtils.createAnimatorSet();
- final AppsCustomizePagedView content = (AppsCustomizePagedView)
- toView.findViewById(R.id.apps_customize_pane_content);
-
- final View page = content.getPageAt(content.getCurrentPage());
- final View revealView = toView.findViewById(R.id.fake_page);
-
- final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets;
- if (isWidgetTray) {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
- } else {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
- }
-
- // Hide the real page background, and swap in the fake one
- content.setPageBackgroundsVisible(false);
- revealView.setVisibility(View.VISIBLE);
- // We need to hide this view as the animation start will be posted.
- revealView.setAlpha(0);
-
- int width = revealView.getMeasuredWidth();
- int height = revealView.getMeasuredHeight();
- float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
-
- revealView.setTranslationY(0);
- revealView.setTranslationX(0);
-
- // Get the y delta between the center of the page and the center of the all apps button
- int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
- getAllAppsButton(), null);
-
- float alpha = 0;
- float xDrift = 0;
- float yDrift = 0;
- if (material) {
- alpha = isWidgetTray ? 0.3f : 1f;
- yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
- xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
- } else {
- yDrift = 2 * height / 3;
- xDrift = 0;
- }
- final float initAlpha = alpha;
-
- layerViews.put(revealView, BUILD_AND_SET_LAYER);
- PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f);
- PropertyValuesHolder panelDriftY =
- PropertyValuesHolder.ofFloat("translationY", yDrift, 0);
- PropertyValuesHolder panelDriftX =
- PropertyValuesHolder.ofFloat("translationX", xDrift, 0);
-
- ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
- panelAlpha, panelDriftY, panelDriftX);
-
- panelAlphaAndDrift.setDuration(revealDuration);
- panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
- mStateAnimation.play(panelAlphaAndDrift);
-
- if (page != null) {
- page.setVisibility(View.VISIBLE);
- layerViews.put(page, BUILD_AND_SET_LAYER);
-
- ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0);
- page.setTranslationY(yDrift);
- pageDrift.setDuration(revealDuration);
- pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
- pageDrift.setStartDelay(itemsAlphaStagger);
- mStateAnimation.play(pageDrift);
-
- page.setAlpha(0f);
- ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f);
- itemsAlpha.setDuration(revealDuration);
- itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
- itemsAlpha.setStartDelay(itemsAlphaStagger);
- mStateAnimation.play(itemsAlpha);
- }
-
- View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator);
- pageIndicators.setAlpha(0.01f);
- ObjectAnimator indicatorsAlpha =
- ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f);
- indicatorsAlpha.setDuration(revealDuration);
- mStateAnimation.play(indicatorsAlpha);
-
- if (material) {
- final View allApps = getAllAppsButton();
- int allAppsButtonSize = LauncherAppState.getInstance().
- getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
- float startRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
- Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2,
- height / 2, startRadius, revealRadius);
- reveal.setDuration(revealDuration);
- reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
- reveal.addListener(new AnimatorListenerAdapter() {
- public void onAnimationStart(Animator animation) {
- if (!isWidgetTray) {
- allApps.setVisibility(View.INVISIBLE);
- }
- }
- public void onAnimationEnd(Animator animation) {
- if (!isWidgetTray) {
- allApps.setVisibility(View.VISIBLE);
- }
- }
- });
- mStateAnimation.play(reveal);
- }
-
- mStateAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
-
- revealView.setVisibility(View.INVISIBLE);
-
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
- content.setPageBackgroundsVisible(true);
-
- // Hide the search bar
- if (mSearchDropTargetBar != null) {
- mSearchDropTargetBar.hideSearchBar(false);
- }
-
- // This can hold unnecessary references to views.
- mStateAnimation = null;
- }
-
- });
-
- if (workspaceAnim != null) {
- mStateAnimation.play(workspaceAnim);
- }
-
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
- 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;
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
-
- revealView.setAlpha(initAlpha);
-
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- }
-
- if (Utilities.isLmpOrAbove()) {
- for (View v : layerViews.keySet()) {
- if (Utilities.isViewAttachedToWindow(v)) v.buildLayer();
- }
- }
- mStateAnimation.start();
- }
- };
- toView.bringToFront();
- toView.setVisibility(View.VISIBLE);
- toView.post(startAnimRunnable);
- } 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 && !LauncherAppState.getInstance().isScreenLarge()) {
- // Hide the search bar
- if (mSearchDropTargetBar != null) {
- 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);
- }
- }
-
- /**
- * 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(Workspace.State toState, final boolean animated,
- final boolean springLoaded, final Runnable onCompleteRunnable) {
-
- if (mStateAnimation != null) {
- mStateAnimation.setDuration(0);
- mStateAnimation.cancel();
- mStateAnimation = null;
- }
-
- boolean material = Utilities.isLmpOrAbove();
- Resources res = getResources();
-
- final int revealDuration = res.getInteger(R.integer.config_appsCustomizeConcealTime);
- final int itemsAlphaStagger =
- res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
-
- final View fromView = mAppsCustomizeTabHost;
- final View toView = mWorkspace;
- Animator workspaceAnim = null;
- final HashMap<View, Integer> layerViews = new HashMap<View, Integer>();
-
- if (toState == Workspace.State.NORMAL) {
- workspaceAnim = mWorkspace.getChangeStateAnimation(
- toState, animated, layerViews);
- } else if (toState == Workspace.State.SPRING_LOADED ||
- toState == Workspace.State.OVERVIEW) {
- workspaceAnim = mWorkspace.getChangeStateAnimation(
- toState, animated, layerViews);
- }
-
- // If for some reason our views aren't initialized, don't animate
- boolean initialized = getAllAppsButton() != null;
-
- if (animated && initialized) {
- mStateAnimation = LauncherAnimUtils.createAnimatorSet();
- if (workspaceAnim != null) {
- mStateAnimation.play(workspaceAnim);
- }
-
- final AppsCustomizePagedView content = (AppsCustomizePagedView)
- fromView.findViewById(R.id.apps_customize_pane_content);
-
- final View page = content.getPageAt(content.getNextPage());
-
- // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases
- int count = content.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = content.getChildAt(i);
- if (child != page) {
- child.setVisibility(View.INVISIBLE);
- }
- }
- final View revealView = fromView.findViewById(R.id.fake_page);
-
- // hideAppsCustomizeHelper is called in some cases when it is already hidden
- // don't perform all these no-op animations. In particularly, this was causing
- // the all-apps button to pop in and out.
- if (fromView.getVisibility() == View.VISIBLE) {
- AppsCustomizePagedView.ContentType contentType = content.getContentType();
- final boolean isWidgetTray =
- contentType == AppsCustomizePagedView.ContentType.Widgets;
-
- if (isWidgetTray) {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
- } else {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
- }
-
- int width = revealView.getMeasuredWidth();
- int height = revealView.getMeasuredHeight();
- float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
-
- // Hide the real page background, and swap in the fake one
- revealView.setVisibility(View.VISIBLE);
- content.setPageBackgroundsVisible(false);
-
- final View allAppsButton = getAllAppsButton();
- revealView.setTranslationY(0);
- int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
- allAppsButton, null);
-
- float xDrift = 0;
- float yDrift = 0;
- if (material) {
- yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
- xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
- } else {
- yDrift = 2 * height / 3;
- xDrift = 0;
- }
-
- layerViews.put(revealView, BUILD_AND_SET_LAYER);
- TimeInterpolator decelerateInterpolator = material ?
- new LogDecelerateInterpolator(100, 0) :
- new DecelerateInterpolator(1f);
-
- // The vertical motion of the apps panel should be delayed by one frame
- // from the conceal animation in order to give the right feel. We correpsondingly
- // shorten the duration so that the slide and conceal end at the same time.
- ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY",
- 0, yDrift);
- panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
- panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
- panelDriftY.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelDriftY);
-
- ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX",
- 0, xDrift);
- panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
- panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
- panelDriftX.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(panelDriftX);
-
- if (isWidgetTray || !material) {
- float finalAlpha = material ? 0.4f : 0f;
- revealView.setAlpha(1f);
- ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha",
- 1f, finalAlpha);
- panelAlpha.setDuration(material ? revealDuration : 150);
- panelAlpha.setInterpolator(decelerateInterpolator);
- panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
- mStateAnimation.play(panelAlpha);
- }
-
- if (page != null) {
- layerViews.put(page, BUILD_AND_SET_LAYER);
-
- ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(page, "translationY",
- 0, yDrift);
- page.setTranslationY(0);
- pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
- pageDrift.setInterpolator(decelerateInterpolator);
- pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
- mStateAnimation.play(pageDrift);
-
- page.setAlpha(1f);
- ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(page, "alpha", 1f, 0f);
- itemsAlpha.setDuration(100);
- itemsAlpha.setInterpolator(decelerateInterpolator);
- mStateAnimation.play(itemsAlpha);
- }
-
- View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator);
- pageIndicators.setAlpha(1f);
- ObjectAnimator indicatorsAlpha =
- LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f);
- indicatorsAlpha.setDuration(revealDuration);
- indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f));
- mStateAnimation.play(indicatorsAlpha);
-
- width = revealView.getMeasuredWidth();
-
- if (material) {
- if (!isWidgetTray) {
- allAppsButton.setVisibility(View.INVISIBLE);
- }
- int allAppsButtonSize = LauncherAppState.getInstance().
- getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
- float finalRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
- Animator reveal =
- LauncherAnimUtils.createCircularReveal(revealView, width / 2,
- height / 2, revealRadius, finalRadius);
- reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
- reveal.setDuration(revealDuration);
- reveal.setStartDelay(itemsAlphaStagger);
-
- reveal.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- revealView.setVisibility(View.INVISIBLE);
- if (!isWidgetTray) {
- allAppsButton.setVisibility(View.VISIBLE);
- }
- }
- });
-
- mStateAnimation.play(reveal);
- }
-
- dispatchOnLauncherTransitionPrepare(fromView, animated, true);
- dispatchOnLauncherTransitionPrepare(toView, animated, true);
- mAppsCustomizeContent.stopScrolling();
- }
-
- mStateAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- fromView.setVisibility(View.GONE);
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
-
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
- content.setPageBackgroundsVisible(true);
- // Unhide side pages
- int count = content.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = content.getChildAt(i);
- child.setVisibility(View.VISIBLE);
- }
-
- // Reset page transforms
- if (page != null) {
- page.setTranslationX(0);
- page.setTranslationY(0);
- page.setAlpha(1);
- }
- content.setCurrentPage(content.getNextPage());
-
- mAppsCustomizeContent.updateCurrentPageScroll();
-
- // This can hold unnecessary references to views.
- mStateAnimation = 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;
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
-
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- }
-
- if (Utilities.isLmpOrAbove()) {
- for (View v : layerViews.keySet()) {
- if (Utilities.isViewAttachedToWindow(v)) v.buildLayer();
- }
- }
- mStateAnimation.start();
- }
- };
- fromView.post(startAnimRunnable);
- } 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);
- }
- }
-
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
@@ -3820,28 +3286,33 @@ public class Launcher extends Activity
SQLiteDatabase.releaseMemory();
// This clears all widget bitmaps from the widget tray
- if (mAppsCustomizeTabHost != null) {
- mAppsCustomizeTabHost.trimMemory();
- }
+ // TODO(hyunyoungs)
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onTrimMemory(level);
}
}
- protected void showWorkspace(boolean animated) {
- showWorkspace(animated, null);
+ @Override
+ public void onStateTransitionHideSearchBar() {
+ // Hide the search bar
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.hideSearchBar(false /* animated */);
+ }
}
- protected void showWorkspace() {
- showWorkspace(true);
+ protected void showWorkspace(boolean animated) {
+ showWorkspace(animated, null);
}
void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
- if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) {
+ boolean changed = mState != State.WORKSPACE ||
+ mWorkspace.getState() != Workspace.State.NORMAL;
+ if (changed) {
boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
mWorkspace.setVisibility(View.VISIBLE);
- hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable);
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
+ animated, onCompleteRunnable);
// Show the search bar (only animate if we were showing the drop target bar in spring
// loaded mode)
@@ -3860,18 +3331,21 @@ public class Launcher extends Activity
// Resume the auto-advance of widgets
mUserPresent = true;
- updateRunning();
+ updateAutoAdvanceState();
- // Send an accessibility event to announce the context change
- getWindow().getDecorView()
- .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (changed) {
+ // Send an accessibility event to announce the context change
+ getWindow().getDecorView()
+ .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- onWorkspaceShown(animated);
+ onWorkspaceShown(animated);
+ }
}
void showOverviewMode(boolean animated) {
mWorkspace.setVisibility(View.VISIBLE);
- hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null);
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
+ animated, null /* onCompleteRunnable */);
mState = State.WORKSPACE;
onWorkspaceShown(animated);
}
@@ -3879,28 +3353,56 @@ public class Launcher extends Activity
public void onWorkspaceShown(boolean animated) {
}
- void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
- boolean resetPageToZero) {
- if (mState != State.WORKSPACE) return;
+ /**
+ * Shows the apps view.
+ */
+ void showAppsView(boolean animated, boolean resetListToTop) {
+ if (resetListToTop) {
+ mAppsView.scrollToTop();
+ }
+ showAppsOrWidgets(animated, State.APPS);
+ }
+ /**
+ * Shows the widgets view.
+ */
+ void showWidgetsView(boolean animated, boolean resetPageToZero) {
+ Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
if (resetPageToZero) {
- mAppsCustomizeTabHost.reset();
+ mWidgetsView.scrollToTop();
}
- showAppsCustomizeHelper(animated, false, contentType);
- mAppsCustomizeTabHost.post(new Runnable() {
+ showAppsOrWidgets(animated, State.WIDGETS);
+
+ mWidgetsView.post(new Runnable() {
@Override
public void run() {
- // We post this in-case the all apps view isn't yet constructed.
- mAppsCustomizeTabHost.requestFocus();
+ mWidgetsView.requestFocus();
}
});
+ }
+
+ /**
+ * Sets up the transition to show the apps/widgets view.
+ */
+ private void showAppsOrWidgets(boolean animated, State toState) {
+ if (mState != State.WORKSPACE) return;
+ if (toState != State.APPS && toState != State.WIDGETS) return;
+
+ if (toState == State.APPS) {
+ mStateTransitionAnimation.startAnimationToAllApps(animated);
+ if (mLauncherCallbacks != null) {
+ mLauncherCallbacks.onAllAppsShown();
+ }
+ } else {
+ mStateTransitionAnimation.startAnimationToWidgets(animated);
+ }
// Change the state *after* we've called all the transition code
- mState = State.APPS_CUSTOMIZE;
+ mState = toState;
// Pause the auto-advance of widgets until we are out of AllApps
mUserPresent = false;
- updateRunning();
+ updateAutoAdvanceState();
closeFolder();
// Send an accessibility event to announce the context change
@@ -3908,25 +3410,33 @@ public class Launcher extends Activity
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
- void enterSpringLoadedDragMode() {
- if (isAllAppsVisible()) {
- hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null);
- mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
+ public void enterSpringLoadedDragMode() {
+ Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
+ mState.name()));
+ if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
+ mState == State.WIDGETS_SPRING_LOADED) {
+ return;
}
+
+ mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
+ true /* animated */, null /* onCompleteRunnable */);
+ mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
}
- void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
+ public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
final Runnable onCompleteRunnable) {
- if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
+ if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (successfulDrop) {
+ // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
+ //
// 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);
+ mWidgetsView.setVisibility(View.GONE);
showWorkspace(true, onCompleteRunnable);
} else {
exitSpringLoadedDragMode();
@@ -3936,11 +3446,12 @@ public class Launcher extends Activity
}
void exitSpringLoadedDragMode() {
- if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
- final boolean animated = true;
- final boolean springLoaded = true;
- showAppsCustomizeHelper(animated, springLoaded);
- mState = State.APPS_CUSTOMIZE;
+ if (mState == State.APPS_SPRING_LOADED) {
+ mStateTransitionAnimation.startAnimationToAllApps(true /* animated */);
+ mState = State.APPS;
+ } else if (mState == State.WIDGETS_SPRING_LOADED) {
+ mStateTransitionAnimation.startAnimationToWidgets(true /* animated */);
+ mState = State.WIDGETS;
}
// Otherwise, we are not in spring loaded mode, so don't do anything.
}
@@ -3957,7 +3468,7 @@ public class Launcher extends Activity
// NO-OP
}
- public View getQsbBar() {
+ public View getOrCreateQsbBar() {
if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
return mLauncherCallbacks.getQsbBar();
}
@@ -4006,19 +3517,31 @@ public class Launcher extends Activity
mQsb.updateAppWidgetOptions(opts);
mQsb.setPadding(0, 0, 0, 0);
mSearchDropTargetBar.addView(mQsb);
+ mSearchDropTargetBar.setQsbSearchBar(mQsb);
}
}
return mQsb;
}
+ private void reinflateQSBIfNecessary() {
+ if (mQsb instanceof LauncherAppWidgetHostView &&
+ ((LauncherAppWidgetHostView) mQsb).isReinflateRequired()) {
+ mSearchDropTargetBar.removeView(mQsb);
+ mQsb = null;
+ mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
+ }
+ }
+
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
final boolean result = super.dispatchPopulateAccessibilityEvent(event);
final List<CharSequence> text = event.getText();
text.clear();
// Populate event with a fake title based on the current state.
- if (mState == State.APPS_CUSTOMIZE) {
- text.add(mAppsCustomizeTabHost.getContentTag());
+ if (mState == State.APPS) {
+ text.add("Apps");
+ } else if (mState == State.WIDGETS) {
+ text.add("Widgets");
} else {
text.add(getString(R.string.all_apps_home_button_label));
}
@@ -4028,7 +3551,7 @@ public class Launcher extends Activity
/**
* Receives notifications when system dialogs are to be closed.
*/
- private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
+ @Thunk class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
closeSystemDialogs();
@@ -4241,9 +3764,8 @@ public class Launcher extends Activity
// Remove the extra empty screen
mWorkspace.removeExtraEmptyScreen(false, false);
- if (!LauncherAppState.isDisableAllApps() &&
- addedApps != null && mAppsCustomizeContent != null) {
- mAppsCustomizeContent.addApps(addedApps);
+ if (addedApps != null && mAppsView != null) {
+ mAppsView.addApps(addedApps);
}
}
@@ -4416,8 +3938,8 @@ public class Launcher extends Activity
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
- Bundle options =
- AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
+ Bundle options = null;
+ // AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -4510,10 +4032,10 @@ public class Launcher extends Activity
*
* Implementation of the method from LauncherModel.Callbacks.
*/
- public void finishBindingItems(final boolean upgradePath) {
+ public void finishBindingItems() {
Runnable r = new Runnable() {
public void run() {
- finishBindingItems(upgradePath);
+ finishBindingItems();
}
};
if (waitUntilResume(r)) {
@@ -4548,14 +4070,10 @@ public class Launcher extends Activity
sPendingAddItem = null;
}
- if (upgradePath) {
- mWorkspace.getUniqueComponents(true, null);
- mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
- }
PackageInstallerCompat.getInstance(this).onFinishBind();
if (mLauncherCallbacks != null) {
- mLauncherCallbacks.finishBindingItems(upgradePath);
+ mLauncherCallbacks.finishBindingItems(false);
}
}
@@ -4612,7 +4130,7 @@ public class Launcher extends Activity
mSearchDropTargetBar.removeView(mQsb);
mQsb = null;
}
- mSearchDropTargetBar.setQsbSearchBar(getQsbBar());
+ mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
}
/**
@@ -4621,24 +4139,12 @@ public class Launcher extends Activity
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(final ArrayList<AppInfo> apps) {
- if (LauncherAppState.isDisableAllApps()) {
- if (mIntentsOnWorkspaceFromUpgradePath != null) {
- if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
- getHotseat().addAllAppsFolder(mIconCache, apps,
- mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
- }
- mIntentsOnWorkspaceFromUpgradePath = null;
- }
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.onPackagesUpdated(
- LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
- }
- } else {
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.setApps(apps);
- mAppsCustomizeContent.onPackagesUpdated(
- LauncherModel.getSortedWidgetsAndShortcuts(this, false /* refresh */));
- }
+ if (mAppsView != null) {
+ mAppsView.setApps(apps);
+ }
+ if (mWidgetsView != null) {
+ mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
+ getPackageManager());
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.bindAllApplications(apps);
@@ -4660,9 +4166,8 @@ public class Launcher extends Activity
return;
}
- if (!LauncherAppState.isDisableAllApps() &&
- mAppsCustomizeContent != null) {
- mAppsCustomizeContent.updateApps(apps);
+ if (mAppsView != null) {
+ mAppsView.updateApps(apps);
}
}
@@ -4776,31 +4281,31 @@ public class Launcher extends Activity
}
// Update AllApps
- if (!LauncherAppState.isDisableAllApps() &&
- mAppsCustomizeContent != null) {
- mAppsCustomizeContent.removeApps(appInfos);
+ if (mAppsView != null) {
+ mAppsView.removeApps(appInfos);
}
}
/**
* A number of packages were updated.
*/
- private ArrayList<Object> mWidgetsAndShortcuts;
+ @Thunk ArrayList<Object> mWidgetsAndShortcuts;
private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
public void run() {
bindPackagesUpdated(mWidgetsAndShortcuts);
mWidgetsAndShortcuts = null;
}
};
+
public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
mWidgetsAndShortcuts = widgetsAndShortcuts;
return;
}
- // Update the widgets pane
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
+ if (mWidgetsView != null) {
+ mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
+ getPackageManager());
}
}
@@ -4910,6 +4415,17 @@ public class Launcher extends Activity
return null;
}
+ /**
+ * Returns whether the launcher callbacks overrides search in all apps.
+ * @return
+ */
+ @Thunk boolean isAllAppsSearchOverridden() {
+ if (mLauncherCallbacks != null) {
+ return mLauncherCallbacks.overrideAllAppsSearch();
+ }
+ return false;
+ }
+
private boolean shouldRunFirstRunActivity() {
return !ActivityManager.isRunningInTestHarness() &&
!mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
@@ -5010,7 +4526,7 @@ public class Launcher extends Activity
editor.apply();
}
- private void showFirstRunClings() {
+ @Thunk void showFirstRunClings() {
// The two first run cling paths are mutually exclusive, if the launcher is preinstalled
// on the device, then we always show the first run cling experience (or if there is no
// launcher2). Otherwise, we prompt the user upon started for migration
@@ -5047,7 +4563,7 @@ public class Launcher extends Activity
if (activityInfo == null) {
return null;
}
- return new AppInfo(this, activityInfo, myUser, mIconCache, null);
+ return new AppInfo(this, activityInfo, myUser, mIconCache);
}
public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
@@ -5093,10 +4609,8 @@ public class Launcher extends Activity
Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
Log.d(TAG, "sFolders.size=" + sFolders.size());
mModel.dumpState();
+ // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.dumpState();
- }
Log.d(TAG, "END launcher3 dump state");
}
diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index c9e277e4c..8ba02ea5f 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -1,15 +1,21 @@
package com.android.launcher3;
import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.util.SparseArray;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.launcher3.LauncherModel.ScreenPosProvider;
+import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -20,10 +26,25 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
public static final int INFO = R.id.action_info;
public static final int UNINSTALL = R.id.action_uninstall;
public static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
+ public static final int MOVE = R.id.action_move;
+
+ enum DragType {
+ ICON,
+ FOLDER,
+ WIDGET
+ }
+
+ public static class DragInfo {
+ DragType dragType;
+ ItemInfo info;
+ View item;
+ }
+
+ private DragInfo mDragInfo = null;
private final SparseArray<AccessibilityAction> mActions =
new SparseArray<AccessibilityAction>();
- private final Launcher mLauncher;
+ @Thunk final Launcher mLauncher;
public LauncherAccessibilityDelegate(Launcher launcher) {
mLauncher = launcher;
@@ -36,6 +57,9 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
launcher.getText(R.string.delete_target_uninstall_label)));
mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
launcher.getText(R.string.action_add_to_workspace)));
+ mActions.put(MOVE, new AccessibilityAction(MOVE,
+ launcher.getText(R.string.action_move)));
+
}
@Override
@@ -49,6 +73,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
|| (item instanceof FolderInfo)) {
// Workspace shortcut / widget
info.addAction(mActions.get(REMOVE));
+ info.addAction(mActions.get(MOVE));
} else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
// App or Widget from customization tray
if (item instanceof AppInfo) {
@@ -69,14 +94,22 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
}
public boolean performAction(View host, ItemInfo item, int action) {
+ Resources res = mLauncher.getResources();
if (action == REMOVE) {
- return DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
+ if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) {
+ announceConfirmation(R.string.item_removed_from_workspace);
+ return true;
+ }
+ return false;
} else if (action == INFO) {
InfoDropTarget.startDetailsActivityForInfo(item, mLauncher);
return true;
} else if (action == UNINSTALL) {
- DeleteDropTarget.uninstallApp(mLauncher, (AppInfo) item);
+ AppInfo info = (AppInfo) item;
+ mLauncher.startApplicationUninstallActivity(info.componentName, info.flags, info.user);
return true;
+ } else if (action == MOVE) {
+ beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
final int preferredPage = mLauncher.getWorkspace().getCurrentPage();
final ScreenPosProvider screenProvider = new ScreenPosProvider() {
@@ -90,20 +123,92 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
final ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
addShortcuts.add(((AppInfo) item).makeShortcut());
mLauncher.showWorkspace(true, new Runnable() {
-
@Override
public void run() {
- mLauncher.getModel().addAndBindAddedWorkspaceApps(
+ mLauncher.getModel().addAndBindAddedWorkspaceItems(
mLauncher, addShortcuts, screenProvider, 0, true);
+ announceConfirmation(R.string.item_added_to_workspace);
}
});
return true;
} else if (item instanceof PendingAddItemInfo) {
mLauncher.getModel().addAndBindPendingItem(
mLauncher, (PendingAddItemInfo) item, screenProvider, 0);
+ announceConfirmation(R.string.item_added_to_workspace);
return true;
}
}
return false;
}
+
+ @Thunk void announceConfirmation(int resId) {
+ announceConfirmation(mLauncher.getResources().getString(resId));
+ }
+
+ @Thunk void announceConfirmation(String confirmation) {
+ mLauncher.getDragLayer().announceForAccessibility(confirmation);
+
+ }
+
+ public boolean isInAccessibleDrag() {
+ return mDragInfo != null;
+ }
+
+ public DragInfo getDragInfo() {
+ return mDragInfo;
+ }
+
+ public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation,
+ String confirmation) {
+ if (!isInAccessibleDrag()) return;
+
+ int[] loc = new int[2];
+ loc[0] = dropLocation.centerX();
+ loc[1] = dropLocation.centerY();
+
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc);
+ mLauncher.getDragController().completeAccessibleDrag(loc);
+
+ endAccessibleDrag();
+ announceConfirmation(confirmation);
+ }
+
+ public void beginAccessibleDrag(View item, ItemInfo info) {
+ mDragInfo = new DragInfo();
+ mDragInfo.info = info;
+ mDragInfo.item = item;
+ mDragInfo.dragType = DragType.ICON;
+ if (info instanceof FolderInfo) {
+ mDragInfo.dragType = DragType.FOLDER;
+ } else if (info instanceof LauncherAppWidgetInfo) {
+ mDragInfo.dragType = DragType.WIDGET;
+ }
+
+ CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
+
+ Rect pos = new Rect();
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
+
+ mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
+ mLauncher.getWorkspace().enableAccessibleDrag(true);
+ mLauncher.getWorkspace().startDrag(cellInfo, true);
+ }
+
+ public boolean onBackPressed() {
+ if (isInAccessibleDrag()) {
+ cancelAccessibleDrag();
+ return true;
+ }
+ return false;
+ }
+
+ private void cancelAccessibleDrag() {
+ mLauncher.getDragController().cancelDrag();
+ endAccessibleDrag();
+ }
+
+ private void endAccessibleDrag() {
+ mDragInfo = null;
+ mLauncher.getWorkspace().enableAccessibleDrag(false);
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 87e9aae15..6e77d0628 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -19,44 +19,38 @@ package com.android.launcher3;
import android.annotation.TargetApi;
import android.app.SearchManager;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Point;
import android.os.Build;
-import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
-import android.view.View.AccessibilityDelegate;
import android.view.WindowManager;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+import com.android.launcher3.util.Thunk;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
- private static final String TAG = "LauncherAppState";
-
- private static final boolean DEBUG = false;
private final AppFilter mAppFilter;
private final BuildInfo mBuildInfo;
- private final LauncherModel mModel;
+ @Thunk final LauncherModel mModel;
private final IconCache mIconCache;
+ private final WidgetPreviewLoader mWidgetCache;
private final boolean mIsScreenLarge;
private final float mScreenDensity;
private final int mLongPressTimeout = 300;
- private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
private boolean mWallpaperChangedSinceLastCheck;
private static WeakReference<LauncherProvider> sLauncherProvider;
@@ -65,7 +59,7 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
private static LauncherAppState INSTANCE;
private DynamicGrid mDynamicGrid;
- private AccessibilityDelegate mAccessibilityDelegate;
+ private LauncherAccessibilityDelegate mAccessibilityDelegate;
public static LauncherAppState getInstance() {
if (INSTANCE == null) {
@@ -103,39 +97,22 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
// set sIsScreenXLarge and mScreenDensity *before* creating icon cache
mIsScreenLarge = isScreenLarge(sContext.getResources());
mScreenDensity = sContext.getResources().getDisplayMetrics().density;
-
- recreateWidgetPreviewDb();
mIconCache = new IconCache(sContext);
+ mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
- launcherApps.addOnAppsChangedCallback(mModel);
+
+ LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- sContext.registerReceiver(mModel, filter);
- filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- sContext.registerReceiver(mModel, filter);
- filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
sContext.registerReceiver(mModel, filter);
-
- // Register for changes to the favorites
- ContentResolver resolver = sContext.getContentResolver();
- resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
- mFavoritesObserver);
- }
-
- public void recreateWidgetPreviewDb() {
- if (mWidgetPreviewCacheDb != null) {
- mWidgetPreviewCacheDb.close();
- }
- mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
}
/**
@@ -146,23 +123,16 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
launcherApps.removeOnAppsChangedCallback(mModel);
PackageInstallerCompat.getInstance(sContext).onStop();
-
- ContentResolver resolver = sContext.getContentResolver();
- resolver.unregisterContentObserver(mFavoritesObserver);
}
/**
- * Receives notifications whenever the user favorites have changed.
+ * Reloads the workspace items from the DB and re-binds the workspace. This should generally
+ * not be called as DB updates are automatically followed by UI update
*/
- 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();
- }
- };
+ public void reloadWorkspace() {
+ mModel.resetLoadedState(false, true);
+ mModel.startLoaderFromBackground();
+ }
LauncherModel setLauncher(Launcher launcher) {
mModel.initialize(launcher);
@@ -171,7 +141,7 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
return mModel;
}
- AccessibilityDelegate getAccessibilityDelegate() {
+ public LauncherAccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
}
@@ -179,7 +149,7 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
return mIconCache;
}
- LauncherModel getModel() {
+ public LauncherModel getModel() {
return mModel;
}
@@ -187,10 +157,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
return mAppFilter == null || mAppFilter.shouldShowApp(componentName);
}
- WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() {
- return mWidgetPreviewCacheDb;
- }
-
static void setLauncherProvider(LauncherProvider provider) {
sLauncherProvider = new WeakReference<LauncherProvider>(provider);
}
@@ -246,6 +212,10 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
return mDynamicGrid;
}
+ public WidgetPreviewLoader getWidgetCache() {
+ return mWidgetCache;
+ }
+
public boolean isScreenLarge() {
return mIsScreenLarge;
}
@@ -283,12 +253,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
Utilities.setIconSize(grid.iconSizePx);
}
- public static boolean isDisableAllApps() {
- // Returns false on non-dogfood builds.
- return getInstance().mBuildInfo.isDogfoodBuild() &&
- Utilities.isPropertyEnabled(Launcher.DISABLE_ALL_APPS_PROPERTY);
- }
-
public static boolean isDogfoodBuild() {
return getInstance().mBuildInfo.isDogfoodBuild();
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index a28fd255a..583f85ad0 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -84,8 +84,10 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
mLauncher.bindPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(mLauncher,
true /* refresh */));
- for (Runnable callback : mProviderChangeListeners) {
- callback.run();
+ if (!mProviderChangeListeners.isEmpty()) {
+ for (Runnable callback : new ArrayList<>(mProviderChangeListeners)) {
+ callback.run();
+ }
}
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index e7f49b2ce..bb4580ce7 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -7,8 +7,6 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
-import java.lang.reflect.Field;
-
/**
* This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
* a common object for describing both framework provided AppWidgets as well as custom widgets
@@ -18,10 +16,10 @@ import java.lang.reflect.Field;
public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
public boolean isCustomWidget = false;
- int spanX = -1;
- int spanY = -1;
- int minSpanX = -1;
- int minSpanY = -1;
+ public int spanX = -1;
+ public int spanY = -1;
+ public int minSpanX = -1;
+ public int minSpanY = -1;
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
@@ -80,10 +78,11 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
return super.loadIcon(context, cache.getFullResIconDpi());
}
- public String toString() {
+ public String toString(PackageManager pm) {
if (isCustomWidget) {
- return "LauncherAppWidgetProviderInfo(" + provider + ")";
+ return "WidgetProviderInfo(" + provider + ")";
}
- return super.toString();
+ return String.format("WidgetProviderInfo provider:%s package:%s short:%s label:%s span(%d, %d) minSpan(%d, %d)",
+ provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm), spanX, spanY, minSpanX, minSpanY);
}
}
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index ddfd70d6b..5f7173fac 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -78,7 +78,7 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
super.onRestore(data, appVersionCode, newState);
// If no favorite was migrated, clear the data and start fresh.
final Cursor c = getContentResolver().query(
- LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, null, null, null, null);
+ LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
hasData = c.moveToNext();
c.close();
} catch (Exception e) {
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 31b434c42..064f4363a 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -19,13 +19,16 @@ import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupHelper;
import android.app.backup.BackupManager;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -51,6 +54,9 @@ import com.android.launcher3.compat.UserManagerCompat;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import com.google.protobuf.nano.MessageNano;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -58,7 +64,6 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.zip.CRC32;
@@ -138,6 +143,8 @@ public class LauncherBackupHelper implements BackupHelper {
private final Context mContext;
private final HashSet<String> mExistingKeys;
private final ArrayList<Key> mKeys;
+ private final ItemTypeMatcher[] mItemTypeMatchers;
+ private final long mUserSerial;
private IconCache mIconCache;
private BackupManager mBackupManager;
@@ -154,6 +161,10 @@ public class LauncherBackupHelper implements BackupHelper {
mExistingKeys = new HashSet<String>();
mKeys = new ArrayList<Key>();
restoreSuccessful = true;
+ mItemTypeMatchers = new ItemTypeMatcher[CommonAppTypeParser.SUPPORTED_TYPE_COUNT];
+
+ UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+ mUserSerial = userManager.getSerialNumberForUser(UserHandleCompat.myUserHandle());
}
private void dataChanged() {
@@ -290,6 +301,12 @@ public class LauncherBackupHelper implements BackupHelper {
if (!restoreSuccessful) {
return;
}
+ if (!initializeIconCache()) {
+ // During restore we do not need an initialized instance of IconCache. We can create
+ // a temporary icon cache here, as the process will be rebooted after restore
+ // is complete.
+ mIconCache = new IconCache(mContext);
+ }
int dataSize = data.size();
if (mBuffer.length < dataSize) {
@@ -445,7 +462,7 @@ public class LauncherBackupHelper implements BackupHelper {
ContentResolver cr = mContext.getContentResolver();
ContentValues values = unpackFavorite(buffer, dataSize);
- cr.insert(Favorites.CONTENT_URI_NO_NOTIFICATION, values);
+ cr.insert(Favorites.CONTENT_URI, values);
}
/**
@@ -594,7 +611,8 @@ public class LauncherBackupHelper implements BackupHelper {
Log.w(TAG, "failed to unpack icon for " + key.name);
}
if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
- IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name), icon, res.dpi);
+ mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
+ "" /* label */, mUserSerial);
}
/**
@@ -612,8 +630,7 @@ public class LauncherBackupHelper implements BackupHelper {
return;
}
final ContentResolver cr = mContext.getContentResolver();
- final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
- final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
+ final WidgetPreviewLoader previewLoader = appState.getWidgetCache();
final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
@@ -629,7 +646,6 @@ public class LauncherBackupHelper implements BackupHelper {
final long id = cursor.getLong(ID_INDEX);
final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
final int spanX = cursor.getInt(SPANX_INDEX);
- final int spanY = cursor.getInt(SPANY_INDEX);
final ComponentName provider = ComponentName.unflattenFromString(providerName);
Key key = null;
String backupKey = null;
@@ -648,11 +664,10 @@ public class LauncherBackupHelper implements BackupHelper {
if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount);
if (backupWidgetCount < MAX_WIDGETS_PER_PASS) {
if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
- previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
- spanY * profile.cellHeightPx, widgetSpacingLayout);
UserHandleCompat user = UserHandleCompat.myUserHandle();
writeRowToBackup(key,
- packWidget(dpi, previewLoader, mIconCache, provider, user),
+ packWidget(dpi, previewLoader,spanX * profile.cellWidthPx,
+ mIconCache, provider, user),
data);
mKeys.add(key);
backupWidgetCount ++;
@@ -689,8 +704,8 @@ public class LauncherBackupHelper implements BackupHelper {
if (icon == null) {
Log.w(TAG, "failed to unpack widget icon for " + key.name);
} else {
- IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider),
- icon, widget.icon.dpi);
+ mIconCache.preloadIcon(ComponentName.unflattenFromString(widget.provider),
+ icon, widget.icon.dpi, widget.label, mUserSerial);
}
}
@@ -756,6 +771,17 @@ public class LauncherBackupHelper implements BackupHelper {
return checksum.getValue();
}
+ /**
+ * @return true if its an hotseat item, that can be replaced during restore.
+ * TODO: Extend check for folders in hotseat.
+ */
+ private boolean isReplaceableHotseatItem(Favorite favorite) {
+ return favorite.container == Favorites.CONTAINER_HOTSEAT
+ && favorite.intent != null
+ && (favorite.itemType == Favorites.ITEM_TYPE_APPLICATION
+ || favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT);
+ }
+
/** Serialize a Favorite for persistence, including a checksum wrapper. */
private Favorite packFavorite(Cursor c) {
Favorite favorite = new Favorite();
@@ -788,9 +814,10 @@ public class LauncherBackupHelper implements BackupHelper {
favorite.title = title;
}
String intentDescription = c.getString(INTENT_INDEX);
+ Intent intent = null;
if (!TextUtils.isEmpty(intentDescription)) {
try {
- Intent intent = Intent.parseUri(intentDescription, 0);
+ intent = Intent.parseUri(intentDescription, 0);
intent.removeExtra(ItemInfo.EXTRA_PROFILE);
favorite.intent = intent.toUri(0);
} catch (URISyntaxException e) {
@@ -806,6 +833,31 @@ public class LauncherBackupHelper implements BackupHelper {
}
}
+ if (isReplaceableHotseatItem(favorite)) {
+ if (intent != null && intent.getComponent() != null) {
+ PackageManager pm = mContext.getPackageManager();
+ ActivityInfo activity = null;;
+ try {
+ activity = pm.getActivityInfo(intent.getComponent(), 0);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Target not found", e);
+ }
+ if (activity == null) {
+ return favorite;
+ }
+ for (int i = 0; i < mItemTypeMatchers.length; i++) {
+ if (mItemTypeMatchers[i] == null) {
+ mItemTypeMatchers[i] = new ItemTypeMatcher(
+ CommonAppTypeParser.getResourceForItemType(i));
+ }
+ if (mItemTypeMatchers[i].matches(activity, pm)) {
+ favorite.targetType = i;
+ break;
+ }
+ }
+ }
+ }
+
return favorite;
}
@@ -813,6 +865,7 @@ public class LauncherBackupHelper implements BackupHelper {
private ContentValues unpackFavorite(byte[] buffer, int dataSize)
throws IOException {
Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
+
ContentValues values = new ContentValues();
values.put(Favorites._ID, favorite.id);
values.put(Favorites.SCREEN, favorite.screen);
@@ -863,8 +916,17 @@ public class LauncherBackupHelper implements BackupHelper {
throw new InvalidBackupException("Widget not in screen bounds, aborting restore");
}
} else {
- // Let LauncherModel know we've been here.
- values.put(LauncherSettings.Favorites.RESTORED, 1);
+ // Check if it is an hotseat item, that can be replaced.
+ if (isReplaceableHotseatItem(favorite)
+ && favorite.targetType != Favorite.TARGET_NONE
+ && favorite.targetType < CommonAppTypeParser.SUPPORTED_TYPE_COUNT) {
+ Log.e(TAG, "Added item type flag");
+ values.put(LauncherSettings.Favorites.RESTORED,
+ 1 | CommonAppTypeParser.encodeItemTypeToFlag(favorite.targetType));
+ } else {
+ // Let LauncherModel know we've been here.
+ values.put(LauncherSettings.Favorites.RESTORED, 1);
+ }
// Verify placement
if (favorite.container == Favorites.CONTAINER_HOTSEAT) {
@@ -915,7 +977,8 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Serialize a widget for persistence, including a checksum wrapper. */
- private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
+ private Widget packWidget(int dpi, WidgetPreviewLoader previewLoader,
+ int previewWidth, IconCache iconCache,
ComponentName provider, UserHandleCompat user) {
final LauncherAppWidgetProviderInfo info =
LauncherModel.getProviderInfo(mContext, provider, user);
@@ -935,7 +998,7 @@ public class LauncherBackupHelper implements BackupHelper {
}
if (info.previewImage != 0) {
widget.preview = new Resource();
- Bitmap preview = previewLoader.generateWidgetPreview(info, null);
+ Bitmap preview = previewLoader.generateWidgetPreview(info, previewWidth, null);
ByteArrayOutputStream os = new ByteArrayOutputStream();
if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
widget.preview.data = os.toByteArray();
@@ -1094,9 +1157,11 @@ public class LauncherBackupHelper implements BackupHelper {
final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
if (appState == null) {
- Throwable stackTrace = new Throwable();
- stackTrace.fillInStackTrace();
- Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
+ if (DEBUG) {
+ Throwable stackTrace = new Throwable();
+ stackTrace.fillInStackTrace();
+ Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
+ }
return false;
}
mIconCache = appState.getIconCache();
@@ -1131,6 +1196,9 @@ public class LauncherBackupHelper implements BackupHelper {
}
private class InvalidBackupException extends IOException {
+
+ private static final long serialVersionUID = 8931456637211665082L;
+
private InvalidBackupException(Throwable cause) {
super(cause);
}
@@ -1139,4 +1207,54 @@ public class LauncherBackupHelper implements BackupHelper {
super(reason);
}
}
+
+ /**
+ * A class to check if an activity can handle one of the intents from a list of
+ * predefined intents.
+ */
+ private class ItemTypeMatcher {
+
+ private final ArrayList<Intent> mIntents;
+
+ ItemTypeMatcher(int xml_res) {
+ mIntents = xml_res == 0 ? new ArrayList<Intent>() : parseIntents(xml_res);
+ }
+
+ private ArrayList<Intent> parseIntents(int xml_res) {
+ ArrayList<Intent> intents = new ArrayList<Intent>();
+ XmlResourceParser parser = mContext.getResources().getXml(xml_res);
+ try {
+ DefaultLayoutParser.beginDocument(parser, DefaultLayoutParser.TAG_RESOLVE);
+ 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;
+ } else if (DefaultLayoutParser.TAG_FAVORITE.equals(parser.getName())) {
+ final String uri = DefaultLayoutParser.getAttributeValue(
+ parser, DefaultLayoutParser.ATTR_URI);
+ intents.add(Intent.parseUri(uri, 0));
+ }
+ }
+ } catch (URISyntaxException | XmlPullParserException | IOException e) {
+ Log.e(TAG, "Unable to parse " + xml_res, e);
+ } finally {
+ parser.close();
+ }
+ return intents;
+ }
+
+ public boolean matches(ActivityInfo activity, PackageManager pm) {
+ for (Intent intent : mIntents) {
+ intent.setPackage(activity.packageName);
+ ResolveInfo info = pm.resolveActivity(intent, 0);
+ if (info != null && (info.activityInfo.name.equals(activity.name)
+ || info.activityInfo.name.equals(activity.targetActivity))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
}
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index d8128d6e5..2fee81c3d 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -50,6 +50,7 @@ public interface LauncherCallbacks {
public void onLauncherProviderChange();
public void finishBindingItems(final boolean upgradePath);
public void onClickAllAppsButton(View v);
+ public void onAllAppsShown();
public void bindAllApplications(ArrayList<AppInfo> apps);
public void onClickFolderIcon(View v);
public void onClickAppShortcut(View v);
@@ -87,6 +88,7 @@ public interface LauncherCallbacks {
public ComponentName getWallpaperPickerComponent();
public boolean overrideWallpaperDimensions();
public boolean isLauncherPreinstalled();
+ public boolean overrideAllAppsSearch();
/**
* Returning true will immediately result in a call to {@link #setLauncherOverlayView(ViewGroup,
@@ -106,4 +108,12 @@ public interface LauncherCallbacks {
public Launcher.LauncherOverlay setLauncherOverlayView(InsettableFrameLayout container,
Launcher.LauncherOverlayCallbacks callbacks);
+ /**
+ * Sets the callbacks to allow any extensions to callback to the launcher.
+ *
+ * @param callbacks A set of callbacks to the Launcher, is actually a LauncherAppsCallback, but
+ * for implementation purposes is passed around as an object.
+ */
+ public void setLauncherAppsCallback(Object callbacks);
+
}
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index ef8e8abcf..2ce8b1c59 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -35,6 +35,8 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.accessibility.AccessibilityManager;
+import com.android.launcher3.util.Thunk;
+
class LauncherClings implements OnClickListener {
private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed";
private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
@@ -49,7 +51,7 @@ class LauncherClings implements OnClickListener {
// New Secure Setting in L
private static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
- private Launcher mLauncher;
+ @Thunk Launcher mLauncher;
private LayoutInflater mInflater;
/** Ctor */
@@ -174,7 +176,7 @@ class LauncherClings implements OnClickListener {
});
}
- private void dismissLongPressCling() {
+ @Thunk void dismissLongPressCling() {
Runnable dismissCb = new Runnable() {
public void run() {
dismissCling(mLauncher.findViewById(R.id.longpress_cling), null,
@@ -185,7 +187,7 @@ class LauncherClings implements OnClickListener {
}
/** Hides the specified Cling */
- private void dismissCling(final View cling, final Runnable postAnimationCb,
+ @Thunk void dismissCling(final View cling, final Runnable postAnimationCb,
final String flag, int duration) {
// To catch cases where siblings of top-level views are made invisible, just check whether
// the cling is directly set to GONE before dismissing it.
diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java
index fe9bd6c23..e4fdbbc7c 100644
--- a/src/com/android/launcher3/LauncherExtension.java
+++ b/src/com/android/launcher3/LauncherExtension.java
@@ -124,6 +124,10 @@ public class LauncherExtension extends Launcher {
}
@Override
+ public void onAllAppsShown() {
+ }
+
+ @Override
public void bindAllApplications(ArrayList<AppInfo> apps) {
}
@@ -246,6 +250,11 @@ public class LauncherExtension extends Launcher {
}
@Override
+ public boolean overrideAllAppsSearch() {
+ return false;
+ }
+
+ @Override
public boolean isLauncherPreinstalled() {
return false;
}
@@ -265,6 +274,11 @@ public class LauncherExtension extends Launcher {
return mLauncherOverlay;
}
+ @Override
+ public void setLauncherAppsCallback(Object callbacks) {
+ // Do nothing
+ }
+
class LauncherExtensionOverlay implements LauncherOverlay {
LauncherOverlayCallbacks mLauncherOverlayCallbacks;
ViewGroup mOverlayView;
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index fa053650f..9dd8dc50c 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -18,23 +18,29 @@ public class LauncherFiles {
public static final String DEFAULT_WALLPAPER_THUMBNAIL_OLD = "default_thumb.jpg";
public static final String LAUNCHER_DB = "launcher.db";
public static final String LAUNCHER_PREFERENCES = "launcher.preferences";
- public static final String LAUNCHES_LOG = "launches.log";
public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
- public static final String STATS_LOG = "stats.log";
public static final String WALLPAPER_CROP_PREFERENCES_KEY =
- WallpaperCropActivity.class.getName();
+ "com.android.launcher3.WallpaperCropActivity";
+ public static final String MANAGED_USER_PREFERENCES_KEY = "com.android.launcher3.managedusers.prefs";
+
public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db";
public static final String WIDGET_PREVIEWS_DB = "widgetpreviews.db";
+ public static final String APP_ICONS_DB = "app_icons.db";
public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
DEFAULT_WALLPAPER_THUMBNAIL,
DEFAULT_WALLPAPER_THUMBNAIL_OLD,
LAUNCHER_DB,
LAUNCHER_PREFERENCES,
- LAUNCHES_LOG,
SHARED_PREFERENCES_KEY + XML,
- STATS_LOG,
WALLPAPER_CROP_PREFERENCES_KEY + XML,
WALLPAPER_IMAGES_DB,
- WIDGET_PREVIEWS_DB));
+ WIDGET_PREVIEWS_DB,
+ MANAGED_USER_PREFERENCES_KEY,
+ APP_ICONS_DB));
+
+ // TODO: Delete these files on upgrade
+ public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList(
+ "launches.log",
+ "stats.log"));
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b19496147..f7df6bc1a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -36,7 +36,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Environment;
@@ -60,6 +59,8 @@ import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.util.Thunk;
import java.lang.ref.WeakReference;
import java.net.URISyntaxException;
@@ -76,7 +77,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.TreeMap;
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
@@ -88,13 +88,9 @@ public class LauncherModel extends BroadcastReceiver
static final boolean DEBUG_LOADERS = false;
private static final boolean DEBUG_RECEIVER = false;
private static final boolean REMOVE_UNRESTORED_ICONS = true;
- private static final boolean ADD_MANAGED_PROFILE_SHORTCUTS = false;
static final String TAG = "Launcher.Model";
- // true = use a "More Apps" folder for non-workspace apps on upgrade
- // false = strew non-workspace apps across the workspace on upgrade
- public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
public static final int LOADER_FLAG_NONE = 0;
public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
@@ -102,19 +98,14 @@ public class LauncherModel extends BroadcastReceiver
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
private static final long INVALID_SCREEN_ID = -1L;
- private final boolean mAppsCanBeOnRemoveableStorage;
+ @Thunk final boolean mAppsCanBeOnRemoveableStorage;
private final boolean mOldContentProviderExists;
- private final LauncherAppState mApp;
- private final Object mLock = new Object();
- private DeferredHandler mHandler = new DeferredHandler();
- private LoaderTask mLoaderTask;
- private boolean mIsLoaderTaskRunning;
-
- /**
- * Maintain a set of packages per user, for which we added a shortcut on the workspace.
- */
- private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
+ @Thunk final LauncherAppState mApp;
+ @Thunk final Object mLock = new Object();
+ @Thunk DeferredHandler mHandler = new DeferredHandler();
+ @Thunk LoaderTask mLoaderTask;
+ @Thunk boolean mIsLoaderTaskRunning;
// Specific runnable types that are run on the main thread deferred handler, this allows us to
// clear all queued binding runnables when the Launcher activity is destroyed.
@@ -123,17 +114,17 @@ public class LauncherModel extends BroadcastReceiver
private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
- private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
+ @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
static {
sWorkerThread.start();
}
- private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
+ @Thunk 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;
+ @Thunk boolean mWorkspaceLoaded;
+ @Thunk boolean mAllAppsLoaded;
// When we are loading pages synchronously, we can't just post the binding of items on the side
// pages as this delays the rotation process. Instead, we wait for a callback from the first
@@ -141,7 +132,7 @@ public class LauncherModel extends BroadcastReceiver
// a normal load, we also clear this set of Runnables.
static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
- private WeakReference<Callbacks> mCallbacks;
+ @Thunk WeakReference<Callbacks> mCallbacks;
// < only access in worker thread >
AllAppsList mBgAllAppsList;
@@ -168,9 +159,6 @@ public class LauncherModel extends BroadcastReceiver
// sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
- // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
- static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
-
// sBgWorkspaceScreens is the ordered set of workspace screens.
static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
@@ -183,12 +171,12 @@ public class LauncherModel extends BroadcastReceiver
// </ only access in worker thread >
- private IconCache mIconCache;
+ @Thunk IconCache mIconCache;
protected int mPreviousConfigMcc;
- private final LauncherAppsCompat mLauncherApps;
- private final UserManagerCompat mUserManager;
+ @Thunk final LauncherAppsCompat mLauncherApps;
+ @Thunk final UserManagerCompat mUserManager;
public interface Callbacks {
public boolean setLoadOnResume();
@@ -199,7 +187,7 @@ public class LauncherModel extends BroadcastReceiver
public void bindScreens(ArrayList<Long> orderedScreenIds);
public void bindAddScreens(ArrayList<Long> orderedScreenIds);
public void bindFolders(HashMap<Long,FolderInfo> folders);
- public void finishBindingItems(boolean upgradePath);
+ public void finishBindingItems();
public void bindAppWidget(LauncherAppWidgetInfo info);
public void bindAllApplications(ArrayList<AppInfo> apps);
public void bindAppsAdded(ArrayList<Long> newScreens,
@@ -266,10 +254,10 @@ public class LauncherModel extends BroadcastReceiver
/** Runs the specified runnable immediately if called from the main thread, otherwise it is
* posted on the main thread handler. */
- private void runOnMainThread(Runnable r) {
+ @Thunk void runOnMainThread(Runnable r) {
runOnMainThread(r, 0);
}
- private void runOnMainThread(Runnable r, int type) {
+ @Thunk void runOnMainThread(Runnable r, int type) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
// If we are on the worker thread, post onto the main handler
mHandler.post(r);
@@ -345,9 +333,9 @@ public class LauncherModel extends BroadcastReceiver
runOnWorkerThread(r);
}
- public void addAndBindAddedWorkspaceApps(final Context context,
+ public void addAndBindAddedWorkspaceItems(final Context context,
final ArrayList<ItemInfo> workspaceApps) {
- addAndBindAddedWorkspaceApps(context, workspaceApps,
+ addAndBindAddedWorkspaceItems(context, workspaceApps,
new ScreenPosProvider() {
@Override
@@ -380,7 +368,7 @@ public class LauncherModel extends BroadcastReceiver
* Find a position on the screen for the given size or adds a new screen.
* @return screenId and the coordinates for the item.
*/
- private static Pair<Long, int[]> findSpaceForItem(
+ @Thunk static Pair<Long, int[]> findSpaceForItem(
Context context,
ScreenPosProvider preferredScreen,
int fallbackStartScreen,
@@ -491,13 +479,7 @@ public class LauncherModel extends BroadcastReceiver
Runnable r = new Runnable() {
public void run() {
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
-
- ArrayList<Long> workspaceScreens = new ArrayList<Long>();
- TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
- for (Integer i : orderedScreens.keySet()) {
- long screenId = orderedScreens.get(i);
- workspaceScreens.add(screenId);
- }
+ ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
// Find appropriate space for the item.
Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
@@ -531,7 +513,7 @@ public class LauncherModel extends BroadcastReceiver
* @param fallbackStartScreen the screen to start search for empty space if
* preferredScreen is not available.
*/
- public void addAndBindAddedWorkspaceApps(final Context context,
+ public void addAndBindAddedWorkspaceItems(final Context context,
final ArrayList<ItemInfo> workspaceApps,
final ScreenPosProvider preferredScreen,
final int fallbackStartScreen,
@@ -549,16 +531,10 @@ public class LauncherModel extends BroadcastReceiver
// Get the list of workspace screens. We need to append to this list and
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
// called.
- ArrayList<Long> workspaceScreens = new ArrayList<Long>();
- TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
- for (Integer i : orderedScreens.keySet()) {
- long screenId = orderedScreens.get(i);
- workspaceScreens.add(screenId);
- }
-
+ ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
synchronized(sBgLock) {
for (ItemInfo item : workspaceApps) {
- if (!allowDuplicate) {
+ if (!allowDuplicate && item instanceof ShortcutInfo) {
// Short-circuit this logic if the icon exists somewhere on the workspace
if (shortcutExists(context, item.title.toString(),
item.getIntent(), item.user)) {
@@ -573,21 +549,21 @@ public class LauncherModel extends BroadcastReceiver
long screenId = coords.first;
int[] cordinates = coords.second;
- ShortcutInfo shortcutInfo;
- if (item instanceof ShortcutInfo) {
- shortcutInfo = (ShortcutInfo) item;
+ ItemInfo itemInfo;
+ if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+ itemInfo = item;
} else if (item instanceof AppInfo) {
- shortcutInfo = ((AppInfo) item).makeShortcut();
+ itemInfo = ((AppInfo) item).makeShortcut();
} else {
throw new RuntimeException("Unexpected info type");
}
// Add the shortcut to the db
- addItemToDatabase(context, shortcutInfo,
+ addItemToDatabase(context, itemInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
- screenId, cordinates[0], cordinates[1], false);
+ screenId, cordinates[0], cordinates[1]);
// Save the ShortcutInfo for binding in the workspace
- addedShortcutsFinal.add(shortcutInfo);
+ addedShortcutsFinal.add(itemInfo);
}
}
@@ -671,7 +647,7 @@ public class LauncherModel extends BroadcastReceiver
long screenId, int cellX, int cellY) {
if (item.container == ItemInfo.NO_ID) {
// From all apps
- addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
+ addItemToDatabase(context, item, container, screenId, cellX, cellY);
} else {
// From somewhere else
moveItemInDatabase(context, item, container, screenId, cellX, cellY);
@@ -737,7 +713,7 @@ public class LauncherModel extends BroadcastReceiver
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 Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
final ContentResolver cr = context.getContentResolver();
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
@@ -763,7 +739,7 @@ public class LauncherModel extends BroadcastReceiver
for (int i = 0; i < count; i++) {
ItemInfo item = items.get(i);
final long itemId = item.id;
- final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
+ final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
ContentValues values = valuesList.get(i);
ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
@@ -982,6 +958,7 @@ public class LauncherModel extends BroadcastReceiver
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 optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
FolderInfo folderInfo = null;
switch (c.getInt(itemTypeIndex)) {
@@ -996,6 +973,7 @@ public class LauncherModel extends BroadcastReceiver
folderInfo.screenId = c.getInt(screenIndex);
folderInfo.cellX = c.getInt(cellXIndex);
folderInfo.cellY = c.getInt(cellYIndex);
+ folderInfo.options = c.getInt(optionsIndex);
return folderInfo;
}
@@ -1010,8 +988,8 @@ public class LauncherModel extends BroadcastReceiver
* 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 long screenId, final int cellX, final int cellY, final boolean notify) {
+ public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
+ final long screenId, final int cellX, final int cellY) {
item.container = container;
item.cellX = cellX;
item.cellY = cellY;
@@ -1034,8 +1012,7 @@ public class LauncherModel extends BroadcastReceiver
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
Runnable r = new Runnable() {
public void run() {
- cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
- LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
+ cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
// Lock on mBgLock *after* the db operation
synchronized (sBgLock) {
@@ -1102,7 +1079,7 @@ public class LauncherModel extends BroadcastReceiver
* @param context
* @param item
*/
- static void deleteItemFromDatabase(Context context, final ItemInfo item) {
+ public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
items.add(item);
deleteItemsFromDatabase(context, items);
@@ -1115,11 +1092,10 @@ public class LauncherModel extends BroadcastReceiver
*/
static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
final ContentResolver cr = context.getContentResolver();
-
Runnable r = new Runnable() {
public void run() {
for (ItemInfo item : items) {
- final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false);
+ final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
cr.delete(uri, null, null);
// Lock on mBgLock *after* the db operation
@@ -1147,7 +1123,6 @@ public class LauncherModel extends BroadcastReceiver
break;
}
sBgItemsIdMap.remove(item.id);
- sBgDbIconCache.remove(item);
}
}
}
@@ -1210,27 +1185,25 @@ public class LauncherModel extends BroadcastReceiver
/**
* Remove the contents of the specified folder from the database
*/
- static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
+ public 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);
+ cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
// Lock on mBgLock *after* the db operation
synchronized (sBgLock) {
sBgItemsIdMap.remove(info.id);
sBgFolders.remove(info.id);
- sBgDbIconCache.remove(info);
sBgWorkspaceItems.remove(info);
}
- cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
+ cr.delete(LauncherSettings.Favorites.CONTENT_URI,
LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
// Lock on mBgLock *after* the db operation
synchronized (sBgLock) {
for (ItemInfo childInfo : info.contents) {
sBgItemsIdMap.remove(childInfo.id);
- sBgDbIconCache.remove(childInfo);
}
}
}
@@ -1443,40 +1416,31 @@ public class LauncherModel extends BroadcastReceiver
}
}
- /** Loads the workspace screens db into a map of Rank -> ScreenId */
- private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) {
+ /**
+ * Loads the workspace screen ids in an ordered list.
+ */
+ @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
- final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
- TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>();
+ // Get screens ordered by rank.
+ final Cursor sc = contentResolver.query(screensUri, null, null, null,
+ LauncherSettings.WorkspaceScreens.SCREEN_RANK);
+ ArrayList<Long> screenIds = new ArrayList<Long>();
try {
- final int idIndex = sc.getColumnIndexOrThrow(
- LauncherSettings.WorkspaceScreens._ID);
- final int rankIndex = sc.getColumnIndexOrThrow(
- LauncherSettings.WorkspaceScreens.SCREEN_RANK);
+ final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
while (sc.moveToNext()) {
try {
- long screenId = sc.getLong(idIndex);
- int rank = sc.getInt(rankIndex);
- orderedScreens.put(rank, screenId);
+ screenIds.add(sc.getLong(idIndex));
} catch (Exception e) {
- Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e, true);
+ Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
+ + " - invalid screens: " + e, true);
}
}
} finally {
sc.close();
}
-
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true);
- ArrayList<String> orderedScreensPairs= new ArrayList<String>();
- for (Integer i : orderedScreens.keySet()) {
- orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }");
- }
- Launcher.addDumpLog(TAG, "11683562 - screens: " +
- TextUtils.join(", ", orderedScreensPairs), true);
- return orderedScreens;
+ return screenIds;
}
public boolean isAllAppsLoaded() {
@@ -1501,17 +1465,14 @@ public class LauncherModel extends BroadcastReceiver
private class LoaderTask implements Runnable {
private Context mContext;
private boolean mIsLaunching;
- private boolean mIsLoadingAndBindingWorkspace;
+ @Thunk boolean mIsLoadingAndBindingWorkspace;
private boolean mStopped;
- private boolean mLoadAndBindStepFinished;
+ @Thunk boolean mLoadAndBindStepFinished;
private int mFlags;
- private HashMap<Object, CharSequence> mLabelCache;
-
LoaderTask(Context context, boolean isLaunching, int flags) {
mContext = context;
mIsLaunching = isLaunching;
- mLabelCache = new HashMap<Object, CharSequence>();
mFlags = flags;
}
@@ -1523,8 +1484,7 @@ public class LauncherModel extends BroadcastReceiver
return mIsLoadingAndBindingWorkspace;
}
- /** Returns whether this is an upgrade path */
- private boolean loadAndBindWorkspace() {
+ private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
// Load the workspace
@@ -1532,20 +1492,18 @@ public class LauncherModel extends BroadcastReceiver
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
- boolean isUpgradePath = false;
if (!mWorkspaceLoaded) {
- isUpgradePath = loadWorkspace();
+ loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
- return isUpgradePath;
+ return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
- bindWorkspace(-1, isUpgradePath);
- return isUpgradePath;
+ bindWorkspace(-1);
}
private void waitForIdle() {
@@ -1614,15 +1572,13 @@ public class LauncherModel extends BroadcastReceiver
// Divide the set of loaded items into those that we are binding synchronously, and
// everything else that is to be bound normally (asynchronously).
- bindWorkspace(synchronousBindPage, false);
+ bindWorkspace(synchronousBindPage);
// XXX: For now, continue posting the binding of AllApps as there are other issues that
// arise from that.
onlyBindAllApps();
}
public void run() {
- boolean isUpgrade = false;
-
synchronized (mLock) {
mIsLoaderTaskRunning = true;
}
@@ -1639,7 +1595,7 @@ public class LauncherModel extends BroadcastReceiver
? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
}
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
- isUpgrade = loadAndBindWorkspace();
+ loadAndBindWorkspace();
if (mStopped) {
break keep_running;
@@ -1659,29 +1615,15 @@ public class LauncherModel extends BroadcastReceiver
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
+ // Remove entries for packages which changed while the launcher was dead.
+ LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews();
+
// 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");
- synchronized (sBgLock) {
- for (Object key : sBgDbIconCache.keySet()) {
- updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
- }
- sBgDbIconCache.clear();
- }
-
- if (LauncherAppState.isDisableAllApps()) {
- // Ensure that all the applications that are in the system are
- // represented on the home screen.
- if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
- verifyApplications();
- }
- }
-
// Clear out this reference, otherwise we end up holding it until all of the
// callback runnables are done.
mContext = null;
@@ -1732,28 +1674,6 @@ public class LauncherModel extends BroadcastReceiver
}
}
- private void verifyApplications() {
- final Context context = mApp.getContext();
-
- // Cross reference all the applications in our apps list with items in the workspace
- ArrayList<ItemInfo> tmpInfos;
- ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
- synchronized (sBgLock) {
- for (AppInfo app : mBgAllAppsList.data) {
- tmpInfos = getItemInfoForComponentName(app.componentName, app.user);
- if (tmpInfos.isEmpty()) {
- // We are missing an application icon, so add this to the workspace
- added.add(app);
- // This is a rare event, so lets log it
- Log.e(TAG, "Missing Application on load: " + app);
- }
- }
- }
- if (!added.isEmpty()) {
- addAndBindAddedWorkspaceApps(context, added);
- }
- }
-
// check & update map of what's occupied; used to discard overlapping/invalid items
private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
LauncherAppState app = LauncherAppState.getInstance();
@@ -1851,13 +1771,11 @@ public class LauncherModel extends BroadcastReceiver
sBgAppWidgets.clear();
sBgFolders.clear();
sBgItemsIdMap.clear();
- sBgDbIconCache.clear();
sBgWorkspaceScreens.clear();
}
}
- /** Returns whether this is an upgrade path */
- private boolean loadWorkspace() {
+ private void loadWorkspace() {
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
@@ -1891,12 +1809,6 @@ public class LauncherModel extends BroadcastReceiver
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
}
- // This code path is for our old migration code and should no longer be exercised
- boolean loadedOldDb = false;
-
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
-
synchronized (sBgLock) {
clearSBgDataStructures();
final HashSet<String> installingPkgs = PackageInstallerCompat
@@ -1904,7 +1816,7 @@ public class LauncherModel extends BroadcastReceiver
final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
final ArrayList<Long> restoredRows = new ArrayList<Long>();
- final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION;
+ final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
@@ -1950,9 +1862,8 @@ public class LauncherModel extends BroadcastReceiver
LauncherSettings.Favorites.RESTORED);
final int profileIdIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.PROFILE_ID);
- //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
- //final int displayModeIndex = c.getColumnIndexOrThrow(
- // LauncherSettings.Favorites.DISPLAY_MODE);
+ final int optionsIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.OPTIONS);
ShortcutInfo info;
String intentDescription;
@@ -1978,6 +1889,7 @@ public class LauncherModel extends BroadcastReceiver
user = mUserManager.getUserForSerialNumber(serialNumber);
int promiseType = c.getInt(restoredIndex);
int disabledState = 0;
+ boolean itemReplaced = false;
if (user == null) {
// User has been deleted remove the item.
itemsToRemove.add(id);
@@ -2009,9 +1921,7 @@ public class LauncherModel extends BroadcastReceiver
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.INTENT,
intent.toUri(0));
- String where = BaseColumns._ID + "= ?";
- String[] args = {Long.toString(id)};
- contentResolver.update(contentUri, values, where, args);
+ updateItem(id, values);
}
}
@@ -2041,10 +1951,27 @@ public class LauncherModel extends BroadcastReceiver
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.RESTORED,
promiseType);
- String where = BaseColumns._ID + "= ?";
- String[] args = {Long.toString(id)};
- contentResolver.update(contentUri, values, where, args);
-
+ updateItem(id, values);
+ } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
+ // This is a common app. Try to replace this.
+ int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
+ CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
+ if (parser.findDefaultApp()) {
+ // Default app found. Replace it.
+ intent = parser.parsedIntent;
+ cn = intent.getComponent();
+ ContentValues values = parser.parsedValues;
+ values.put(LauncherSettings.Favorites.RESTORED, 0);
+ updateItem(id, values);
+ restored = false;
+ itemReplaced = true;
+
+ } else if (REMOVE_UNRESTORED_ICONS) {
+ Launcher.addDumpLog(TAG,
+ "Unrestored package removed: " + cn, true);
+ itemsToRemove.add(id);
+ continue;
+ }
} else if (REMOVE_UNRESTORED_ICONS) {
Launcher.addDumpLog(TAG,
"Unrestored package removed: " + cn, true);
@@ -2090,12 +2017,26 @@ public class LauncherModel extends BroadcastReceiver
continue;
}
- if (restored) {
+ container = c.getInt(containerIndex);
+ boolean useLowResIcon = container >= 0 &&
+ c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
+
+ if (itemReplaced) {
+ if (user.equals(UserHandleCompat.myUserHandle())) {
+ info = getAppShortcutInfo(manager, intent, user, context, null,
+ iconIndex, titleIndex, false, useLowResIcon);
+ } else {
+ // Don't replace items for other profiles.
+ itemsToRemove.add(id);
+ continue;
+ }
+ } else if (restored) {
if (user.equals(UserHandleCompat.myUserHandle())) {
Launcher.addDumpLog(TAG,
"constructing info for partially restored package",
true);
- info = getRestoredItemInfo(c, titleIndex, intent, promiseType);
+ info = getRestoredItemInfo(c, titleIndex, intent,
+ promiseType, useLowResIcon);
intent = getRestoredItemIntent(c, context, intent);
} else {
// Don't restore items for other profiles.
@@ -2104,8 +2045,8 @@ public class LauncherModel extends BroadcastReceiver
}
} else if (itemType ==
LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- info = getShortcutInfo(manager, intent, user, context, c,
- iconIndex, titleIndex, mLabelCache, allowMissingTarget);
+ info = getAppShortcutInfo(manager, intent, user, context, c,
+ iconIndex, titleIndex, allowMissingTarget, useLowResIcon);
} else {
info = getShortcutInfo(c, context, iconTypeIndex,
iconPackageIndex, iconResourceIndex, iconIndex,
@@ -2127,7 +2068,6 @@ public class LauncherModel extends BroadcastReceiver
if (info != null) {
info.id = id;
info.intent = intent;
- container = c.getInt(containerIndex);
info.container = container;
info.screenId = c.getInt(screenIndex);
info.cellX = c.getInt(cellXIndex);
@@ -2160,10 +2100,6 @@ public class LauncherModel extends BroadcastReceiver
break;
}
sBgItemsIdMap.put(info.id, info);
-
- // now that we've loaded everthing re-save it with the
- // icon in case it disappears somehow.
- queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
} else {
throw new RuntimeException("Unexpected null ShortcutInfo");
}
@@ -2182,6 +2118,7 @@ public class LauncherModel extends BroadcastReceiver
folderInfo.cellY = c.getInt(cellYIndex);
folderInfo.spanX = 1;
folderInfo.spanY = 1;
+ folderInfo.options = c.getInt(optionsIndex);
// check & update map of what's occupied
if (!checkItemPlacement(occupied, folderInfo)) {
@@ -2326,9 +2263,7 @@ public class LauncherModel extends BroadcastReceiver
providerName);
values.put(LauncherSettings.Favorites.RESTORED,
appWidgetInfo.restoreStatus);
- String where = BaseColumns._ID + "= ?";
- String[] args = {Long.toString(id)};
- contentResolver.update(contentUri, values, where, args);
+ updateItem(id, values);
}
}
sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
@@ -2349,7 +2284,7 @@ public class LauncherModel extends BroadcastReceiver
// Break early if we've stopped loading
if (mStopped) {
clearSBgDataStructures();
- return false;
+ return;
}
if (itemsToRemove.size() > 0) {
@@ -2362,8 +2297,7 @@ public class LauncherModel extends BroadcastReceiver
}
// Don't notify content observers
try {
- client.delete(LauncherSettings.Favorites.getContentUri(id, false),
- null, null);
+ client.delete(LauncherSettings.Favorites.getContentUri(id), null, null);
} catch (RemoteException e) {
Log.w(TAG, "Could not remove id = " + id);
}
@@ -2382,7 +2316,7 @@ public class LauncherModel extends BroadcastReceiver
selectionBuilder.append(")");
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.RESTORED, 0);
- updater.update(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
+ updater.update(LauncherSettings.Favorites.CONTENT_URI,
values, selectionBuilder.toString(), null);
} catch (RemoteException e) {
Log.w(TAG, "Could not update restored rows");
@@ -2395,63 +2329,29 @@ public class LauncherModel extends BroadcastReceiver
null, sWorker);
}
- if (loadedOldDb) {
- long maxScreenId = 0;
- // If we're importing we use the old screen order.
- for (ItemInfo item: sBgItemsIdMap.values()) {
- long screenId = item.screenId;
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- !sBgWorkspaceScreens.contains(screenId)) {
- sBgWorkspaceScreens.add(screenId);
- if (screenId > maxScreenId) {
- maxScreenId = screenId;
- }
- }
- }
- Collections.sort(sBgWorkspaceScreens);
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true);
- Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
- TextUtils.join(", ", sBgWorkspaceScreens), true);
-
- LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
- updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+ sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
+ TextUtils.join(", ", sBgWorkspaceScreens), true);
- // Update the max item id after we load an old db
- long maxItemId = 0;
- // If we're importing we use the old screen order.
- for (ItemInfo item: sBgItemsIdMap.values()) {
- maxItemId = Math.max(maxItemId, item.id);
- }
- LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
- } else {
- TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
- for (Integer i : orderedScreens.keySet()) {
- sBgWorkspaceScreens.add(orderedScreens.get(i));
- }
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
- TextUtils.join(", ", sBgWorkspaceScreens), true);
-
- // Remove any empty screens
- ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
- for (ItemInfo item: sBgItemsIdMap.values()) {
- long screenId = item.screenId;
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- unusedScreens.contains(screenId)) {
- unusedScreens.remove(screenId);
- }
+ // Remove any empty screens
+ ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
+ for (ItemInfo item: sBgItemsIdMap.values()) {
+ long screenId = item.screenId;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ unusedScreens.contains(screenId)) {
+ unusedScreens.remove(screenId);
}
+ }
- // If there are any empty screens remove them, and update.
- if (unusedScreens.size() != 0) {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
- TextUtils.join(", ", unusedScreens), true);
+ // If there are any empty screens remove them, and update.
+ if (unusedScreens.size() != 0) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
+ TextUtils.join(", ", unusedScreens), true);
- sBgWorkspaceScreens.removeAll(unusedScreens);
- updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
- }
+ sBgWorkspaceScreens.removeAll(unusedScreens);
+ updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
}
if (DEBUG_LOADERS) {
@@ -2480,7 +2380,17 @@ public class LauncherModel extends BroadcastReceiver
}
}
}
- return loadedOldDb;
+ }
+
+ /**
+ * Partially updates the item without any notification. Must be called on the worker thread.
+ */
+ private void updateItem(long itemId, ContentValues update) {
+ mContext.getContentResolver().update(
+ LauncherSettings.Favorites.CONTENT_URI,
+ update,
+ BaseColumns._ID + "= ?",
+ new String[]{Long.toString(itemId)});
}
/** Filters the set of items who are directly or indirectly (via another container) on the
@@ -2677,7 +2587,7 @@ public class LauncherModel extends BroadcastReceiver
/**
* Binds all loaded data to actual views on the main thread.
*/
- private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
+ private void bindWorkspace(int synchronizeBindPage) {
final long t = SystemClock.uptimeMillis();
Runnable r;
@@ -2781,7 +2691,7 @@ public class LauncherModel extends BroadcastReceiver
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
- callbacks.finishBindingItems(isUpgradePath);
+ callbacks.finishBindingItems();
}
// If we're profiling, ensure this is the last thing in the queue.
@@ -2885,39 +2795,55 @@ public class LauncherModel extends BroadcastReceiver
if (apps == null || apps.isEmpty()) {
return;
}
- // Sort the applications by name
- final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
- Collections.sort(apps,
- new LauncherModel.ShortcutNameComparator(mLabelCache));
- if (DEBUG_LOADERS) {
- Log.d(TAG, "sort took "
- + (SystemClock.uptimeMillis()-sortTime) + "ms");
+
+ // Update icon cache
+ HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
+
+ // If any package icon has changed (app was updated while launcher was dead),
+ // update the corresponding shortcuts.
+ if (!updatedPackages.isEmpty()) {
+ final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
+ synchronized (sBgLock) {
+ for (ItemInfo info : sBgItemsIdMap.values()) {
+ if (info instanceof ShortcutInfo && user.equals(info.user)
+ && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.getTargetComponent();
+ if (cn != null && updatedPackages.contains(cn.getPackageName())) {
+ si.updateIcon(mIconCache);
+ updates.add(si);
+ }
+ }
+ }
+ }
+
+ if (!updates.isEmpty()) {
+ final UserHandleCompat userFinal = user;
+ mHandler.post(new Runnable() {
+
+ public void run() {
+ Callbacks cb = getCallback();
+ if (cb != null) {
+ cb.bindShortcutsChanged(
+ updates, new ArrayList<ShortcutInfo>(), userFinal);
+ }
+ }
+ });
+ }
}
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i);
// This builds the icon bitmaps.
- mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));
+ mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
}
- if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
- // Add shortcuts for packages which were installed while launcher was dead.
- String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
- + mUserManager.getSerialNumberForUser(user);
- Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
- HashSet<String> newPackageSet = new HashSet<String>();
-
- for (LauncherActivityInfoCompat info : apps) {
- String packageName = info.getComponentName().getPackageName();
- if (!packagesAdded.contains(packageName)
- && !newPackageSet.contains(packageName)) {
- InstallShortcutReceiver.queueInstallShortcut(info, mContext);
- }
- newPackageSet.add(packageName);
+ if (!user.equals(UserHandleCompat.myUserHandle())) {
+ ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
+ if (heuristic != null) {
+ heuristic.processUserApps(apps);
}
-
- prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
}
}
// Huh? Shouldn't this be inside the Runnable below?
@@ -2940,6 +2866,8 @@ public class LauncherModel extends BroadcastReceiver
}
}
});
+ // Cleanup any data stored for a deleted user.
+ ManagedProfileHeuristic.processAllUsers(profiles, mContext);
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
@@ -2962,7 +2890,7 @@ public class LauncherModel extends BroadcastReceiver
sWorker.post(task);
}
- private class AppsAvailabilityCheck extends BroadcastReceiver {
+ @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -3027,68 +2955,43 @@ public class LauncherModel extends BroadcastReceiver
final String[] packages = mPackages;
final int N = packages.length;
switch (mOp) {
- case OP_ADD:
+ case OP_ADD: {
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
- mIconCache.remove(packages[i], mUser);
+ mIconCache.updateIconsForPkg(packages[i], mUser);
mBgAllAppsList.addPackage(context, packages[i], mUser);
}
- // Auto add shortcuts for added packages.
- if (ADD_MANAGED_PROFILE_SHORTCUTS
- && !UserHandleCompat.myUserHandle().equals(mUser)) {
- SharedPreferences prefs = context.getSharedPreferences(
- LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
- String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
- + mUserManager.getSerialNumberForUser(mUser);
- Set<String> shortcutSet = new HashSet<String>(
- prefs.getStringSet(shortcutsSetKey,Collections.EMPTY_SET));
-
- for (int i=0; i<N; i++) {
- if (!shortcutSet.contains(packages[i])) {
- shortcutSet.add(packages[i]);
- List<LauncherActivityInfoCompat> activities =
- mLauncherApps.getActivityList(packages[i], mUser);
- if (activities != null && !activities.isEmpty()) {
- InstallShortcutReceiver.queueInstallShortcut(
- activities.get(0), context);
- }
- }
- }
-
- prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+ ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+ if (heuristic != null) {
+ heuristic.processPackageAdd(mPackages);
}
break;
+ }
case OP_UPDATE:
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+ mIconCache.updateIconsForPkg(packages[i], mUser);
mBgAllAppsList.updatePackage(context, packages[i], mUser);
- WidgetPreviewLoader.removePackageFromDb(
- mApp.getWidgetPreviewCacheDb(), packages[i]);
+ mApp.getWidgetCache().removePackage(packages[i], mUser);
}
break;
- case OP_REMOVE:
- // Remove the packageName for the set of auto-installed shortcuts. This
- // will ensure that the shortcut when the app is installed again.
- if (ADD_MANAGED_PROFILE_SHORTCUTS
- && !UserHandleCompat.myUserHandle().equals(mUser)) {
- SharedPreferences prefs = context.getSharedPreferences(
- LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
- String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
- + mUserManager.getSerialNumberForUser(mUser);
- HashSet<String> shortcutSet = new HashSet<String>(
- prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET));
- shortcutSet.removeAll(Arrays.asList(mPackages));
- prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+ case OP_REMOVE: {
+ ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+ if (heuristic != null) {
+ heuristic.processPackageRemoved(mPackages);
+ }
+ for (int i=0; i<N; i++) {
+ if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+ mIconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
+ }
case OP_UNAVAILABLE:
- boolean clearCache = mOp == OP_REMOVE;
for (int i=0; i<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
- mBgAllAppsList.removePackage(packages[i], mUser, clearCache);
- WidgetPreviewLoader.removePackageFromDb(
- mApp.getWidgetPreviewCacheDb(), packages[i]);
+ mBgAllAppsList.removePackage(packages[i], mUser);
+ mApp.getWidgetCache().removePackage(packages[i], mUser);
}
break;
}
@@ -3120,13 +3023,7 @@ public class LauncherModel extends BroadcastReceiver
new HashMap<ComponentName, AppInfo>();
if (added != null) {
- // Ensure that we add all the workspace applications to the db
- if (LauncherAppState.isDisableAllApps()) {
- final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
- addAndBindAddedWorkspaceApps(context, addedInfos);
- } else {
- addAppsToAllApps(context, added);
- }
+ addAppsToAllApps(context, added);
for (AppInfo ai : added) {
addedOrUpdatedApps.put(ai.componentName, ai);
}
@@ -3179,7 +3076,6 @@ public class LauncherModel extends BroadcastReceiver
AppInfo appInfo = addedOrUpdatedApps.get(cn);
if (si.isPromise()) {
- mIconCache.deletePreloadedIcon(cn, mUser);
if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
// Auto install icon
PackageManager pm = context.getPackageManager();
@@ -3210,6 +3106,9 @@ public class LauncherModel extends BroadcastReceiver
si.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
& ~ShortcutInfo.FLAG_AUTOINTALL_ICON
& ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+ if (appInfo != null) {
+ si.flags = appInfo.flags;
+ }
infoUpdated = true;
si.updateIcon(mIconCache);
@@ -3392,7 +3291,7 @@ public class LauncherModel extends BroadcastReceiver
return widgetsAndShortcuts;
}
- private static boolean isPackageDisabled(Context context, String packageName,
+ @Thunk static boolean isPackageDisabled(Context context, String packageName,
UserHandleCompat user) {
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
return !launcherApps.isPackageEnabledForProfile(packageName, user);
@@ -3424,10 +3323,10 @@ public class LauncherModel extends BroadcastReceiver
* to a package that is not yet installed on the system.
*/
public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent,
- int promiseType) {
+ int promiseType, boolean useLowResIcon) {
final ShortcutInfo info = new ShortcutInfo();
info.user = UserHandleCompat.myUserHandle();
- mIconCache.getTitleAndIcon(info, intent, info.user, true);
+ mIconCache.getTitleAndIcon(info, intent, info.user, useLowResIcon);
if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
String title = (cursor != null) ? cursor.getString(titleIndex) : null;
@@ -3455,7 +3354,7 @@ public class LauncherModel extends BroadcastReceiver
* Make an Intent object for a restored application or shortcut item that points
* to the market page for the item.
*/
- private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
+ @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
ComponentName componentName = intent.getComponent();
return getMarketIntent(componentName.getPackageName());
}
@@ -3470,22 +3369,13 @@ public class LauncherModel extends BroadcastReceiver
}
/**
- * 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,
- UserHandleCompat user, Context context) {
- return getShortcutInfo(manager, intent, user, context, null, -1, -1, null, false);
- }
-
- /**
* 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,
+ public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
- HashMap<Object, CharSequence> labelCache, boolean allowMissingTarget) {
+ boolean allowMissingTarget, boolean useLowResIcon) {
if (user == null) {
Log.d(TAG, "Null user found in getShortcutInfo");
return null;
@@ -3507,52 +3397,29 @@ public class LauncherModel extends BroadcastReceiver
}
final ShortcutInfo info = new ShortcutInfo();
-
- // 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.
- Bitmap icon = mIconCache.getIcon(componentName, lai, labelCache);
-
- // the db
- if (icon == null) {
- if (c != null) {
- icon = getIconFromCursor(c, iconIndex, context);
- }
+ mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
+ if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
+ Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
+ info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
}
- // the fallback icon
- if (icon == null) {
- icon = mIconCache.getDefaultIcon(user);
- info.usingFallbackIcon = true;
- }
- info.setIcon(icon);
- // From the cache.
- if (labelCache != null) {
- info.title = labelCache.get(componentName);
- }
-
- // from the resource
- if (info.title == null && lai != null) {
- info.title = lai.getLabel();
- if (labelCache != null) {
- labelCache.put(componentName, info.title);
- }
- }
// from the db
- if (info.title == null) {
- if (c != null) {
- info.title = c.getString(titleIndex);
- }
+ if (TextUtils.isEmpty(info.title) && 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;
info.user = user;
info.contentDescription = mUserManager.getBadgedLabelForUser(
info.title.toString(), info.user);
+ if (lai != null) {
+ info.flags = AppInfo.initFlags(lai);
+ }
return info;
}
@@ -3585,7 +3452,7 @@ public class LauncherModel extends BroadcastReceiver
return new ArrayList<ItemInfo>(filtered);
}
- private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
+ @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
final UserHandleCompat user) {
ItemInfoFilter filter = new ItemInfoFilter() {
@Override
@@ -3603,7 +3470,7 @@ public class LauncherModel extends BroadcastReceiver
/**
* Make an ShortcutInfo object for a shortcut that isn't an application.
*/
- private ShortcutInfo getShortcutInfo(Cursor c, Context context,
+ @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
int titleIndex) {
@@ -3627,7 +3494,7 @@ public class LauncherModel extends BroadcastReceiver
icon = Utilities.createIconBitmap(packageName, resourceName, mIconCache, context);
// the db
if (icon == null) {
- icon = getIconFromCursor(c, iconIndex, context);
+ icon = Utilities.createIconBitmap(c, iconIndex, context);
}
// the fallback icon
if (icon == null) {
@@ -3636,7 +3503,7 @@ public class LauncherModel extends BroadcastReceiver
}
break;
case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
- icon = getIconFromCursor(c, iconIndex, context);
+ icon = Utilities.createIconBitmap(c, iconIndex, context);
if (icon == null) {
icon = mIconCache.getDefaultIcon(info.user);
info.customIcon = false;
@@ -3655,22 +3522,6 @@ public class LauncherModel extends BroadcastReceiver
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 infoFromShortcutIntent(Context context, Intent data) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
@@ -3719,50 +3570,11 @@ public class LauncherModel extends BroadcastReceiver
return info;
}
- boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
- int iconIndex) {
- // If apps can't be on SD, don't even bother.
- if (!mAppsCanBeOnRemoveableStorage) {
- 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<Long, FolderInfo> folders, long id) {
+ @Thunk static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
// See if a placeholder was created for us already
FolderInfo folderInfo = folders.get(id);
if (folderInfo == null) {
@@ -3773,79 +3585,13 @@ public class LauncherModel extends BroadcastReceiver
return folderInfo;
}
- public static final Comparator<AppInfo> getAppNameComparator() {
- final Collator collator = Collator.getInstance();
- return new Comparator<AppInfo>() {
- public final int compare(AppInfo a, AppInfo b) {
- if (a.user.equals(b.user)) {
- int result = collator.compare(a.title.toString().trim(),
- b.title.toString().trim());
- if (result == 0) {
- result = a.componentName.compareTo(b.componentName);
- }
- return result;
- } else {
- // TODO Need to figure out rules for sorting
- // profiles, this puts work second.
- return a.user.toString().compareTo(b.user.toString());
- }
- }
- };
- }
- public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
- = new Comparator<AppInfo>() {
- public final int compare(AppInfo a, AppInfo b) {
- if (a.firstInstallTime < b.firstInstallTime) return 1;
- if (a.firstInstallTime > b.firstInstallTime) return -1;
- return 0;
- }
- };
- 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<LauncherActivityInfoCompat> {
- private Collator mCollator;
- private HashMap<Object, CharSequence> mLabelCache;
- ShortcutNameComparator(PackageManager pm) {
- mLabelCache = new HashMap<Object, CharSequence>();
- mCollator = Collator.getInstance();
- }
- ShortcutNameComparator(HashMap<Object, CharSequence> labelCache) {
- mLabelCache = labelCache;
- mCollator = Collator.getInstance();
- }
- public final int compare(LauncherActivityInfoCompat a, LauncherActivityInfoCompat b) {
- String labelA, labelB;
- ComponentName keyA = a.getComponentName();
- ComponentName keyB = b.getComponentName();
- if (mLabelCache.containsKey(keyA)) {
- labelA = mLabelCache.get(keyA).toString();
- } else {
- labelA = a.getLabel().toString().trim();
-
- mLabelCache.put(keyA, labelA);
- }
- if (mLabelCache.containsKey(keyB)) {
- labelB = mLabelCache.get(keyB).toString();
- } else {
- labelB = b.getLabel().toString().trim();
-
- mLabelCache.put(keyB, labelB);
- }
- return mCollator.compare(labelA, labelB);
- }
- };
public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
private final AppWidgetManagerCompat mManager;
private final PackageManager mPackageManager;
private final HashMap<Object, String> mLabelCache;
private final Collator mCollator;
- WidgetAndShortcutNameComparator(Context context) {
+ public WidgetAndShortcutNameComparator(Context context) {
mManager = AppWidgetManagerCompat.getInstance(context);
mPackageManager = context.getPackageManager();
mLabelCache = new HashMap<Object, String>();
@@ -3894,4 +3640,13 @@ public class LauncherModel extends BroadcastReceiver
public Callbacks getCallback() {
return mCallbacks != null ? mCallbacks.get() : null;
}
+
+ /**
+ * @return {@link FolderInfo} if its already loaded.
+ */
+ public FolderInfo findFolderById(Long folderId) {
+ synchronized (sBgLock) {
+ return sBgFolders.get(folderId);
+ }
+ }
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 1040b1173..f9f5ae1bb 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.annotation.TargetApi;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
@@ -29,14 +30,19 @@ import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
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.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
import android.os.StrictMode;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -46,6 +52,7 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.Thunk;
import java.io.File;
import java.net.URISyntaxException;
@@ -57,20 +64,19 @@ public class LauncherProvider extends ContentProvider {
private static final String TAG = "Launcher.LauncherProvider";
private static final boolean LOGD = false;
- private static final int MIN_DATABASE_VERSION = 12;
- private static final int DATABASE_VERSION = 22;
+ private static final int DATABASE_VERSION = 24;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = ProviderConfig.AUTHORITY;
- static final String TABLE_FAVORITES = "favorites";
- static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
- static final String PARAMETER_NOTIFY = "notify";
- static final String UPGRADED_FROM_OLD_DATABASE = "UPGRADED_FROM_OLD_DATABASE";
+ static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
+ static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
+ private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
+
private LauncherProviderChangeListener mListener;
/**
@@ -126,7 +132,7 @@ public class LauncherProvider extends ContentProvider {
return result;
}
- private static long dbInsertAndCheck(DatabaseHelper helper,
+ @Thunk static long dbInsertAndCheck(DatabaseHelper helper,
SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
if (values == null) {
throw new RuntimeException("Error: attempting to insert null values");
@@ -144,7 +150,8 @@ public class LauncherProvider extends ContentProvider {
// In very limited cases, we support system|signature permission apps to add to the db
String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
- if (externalAdd != null && "true".equals(externalAdd)) {
+ final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd);
+ if (isExternalAll) {
if (!mOpenHelper.initializeExternalAdd(initialValues)) {
return null;
}
@@ -156,7 +163,14 @@ public class LauncherProvider extends ContentProvider {
if (rowId < 0) return null;
uri = ContentUris.withAppendedId(uri, rowId);
- sendNotify(uri);
+ notifyListeners();
+
+ if (isExternalAll) {
+ LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+ if (app != null) {
+ app.reloadWorkspace();
+ }
+ }
return uri;
}
@@ -181,7 +195,7 @@ public class LauncherProvider extends ContentProvider {
db.endTransaction();
}
- sendNotify(uri);
+ notifyListeners();
return values.length;
}
@@ -205,7 +219,7 @@ public class LauncherProvider extends ContentProvider {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count = db.delete(args.table, args.where, args.args);
- if (count > 0) sendNotify(uri);
+ if (count > 0) notifyListeners();
return count;
}
@@ -217,17 +231,12 @@ public class LauncherProvider extends ContentProvider {
addModifiedTime(values);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count = db.update(args.table, values, args.where, args.args);
- if (count > 0) sendNotify(uri);
+ if (count > 0) notifyListeners();
return count;
}
- private void sendNotify(Uri uri) {
- String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
- if (notify == null || "true".equals(notify)) {
- getContext().getContentResolver().notifyChange(uri, null);
- }
-
+ private void notifyListeners() {
// always notify the backup agent
LauncherBackupAgentHelper.dataChanged(getContext());
if (mListener != null) {
@@ -235,7 +244,7 @@ public class LauncherProvider extends ContentProvider {
}
}
- private static void addModifiedTime(ContentValues values) {
+ @Thunk static void addModifiedTime(ContentValues values) {
values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
}
@@ -251,12 +260,6 @@ public class LauncherProvider extends ContentProvider {
return mOpenHelper.generateNewScreenId();
}
- // This is only required one time while loading the workspace during the
- // upgrade path, and should never be called from anywhere else.
- public void updateMaxScreenId(long maxScreenId) {
- mOpenHelper.updateMaxScreenId(maxScreenId);
- }
-
/**
* Clears all the data for a fresh start.
*/
@@ -274,9 +277,10 @@ public class LauncherProvider extends ContentProvider {
/**
* Loads the default workspace based on the following priority scheme:
- * 1) From a package provided by play store
- * 2) From a partner configuration APK, already in the system image
- * 3) The default configuration for the particular device
+ * 1) From the app restrictions
+ * 2) From a package provided by play store
+ * 3) From a partner configuration APK, already in the system image
+ * 4) The default configuration for the particular device
*/
synchronized public void loadDefaultFavoritesIfNecessary() {
String spKey = LauncherAppState.getSharedPreferencesKey();
@@ -285,9 +289,11 @@ public class LauncherProvider extends ContentProvider {
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
- AutoInstallsLayout loader = AutoInstallsLayout.get(getContext(),
- mOpenHelper.mAppWidgetHost, mOpenHelper);
-
+ AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction();
+ if (loader == null) {
+ loader = AutoInstallsLayout.get(getContext(),
+ mOpenHelper.mAppWidgetHost, mOpenHelper);
+ }
if (loader == null) {
final Partner partner = Partner.get(getContext().getPackageManager());
if (partner != null && partner.hasDefaultLayout()) {
@@ -321,6 +327,40 @@ public class LauncherProvider extends ContentProvider {
}
}
+ /**
+ * Creates workspace loader from an XML resource listed in the app restrictions.
+ *
+ * @return the loader if the restrictions are set and the resource exists; null otherwise.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+ private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
+ // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
+ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ return null;
+ }
+
+ Context ctx = getContext();
+ UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
+ Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
+ if (bundle == null) {
+ return null;
+ }
+
+ String packageName = bundle.getString(RESTRICTION_PACKAGE_NAME);
+ if (packageName != null) {
+ try {
+ Resources targetResources = ctx.getPackageManager()
+ .getResourcesForApplication(packageName);
+ return AutoInstallsLayout.get(ctx, packageName, targetResources,
+ mOpenHelper.mAppWidgetHost, mOpenHelper);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Target package for restricted profile not found", e);
+ return null;
+ }
+ }
+ return null;
+ }
+
private DefaultLayoutParser getDefaultLayoutParser() {
int defaultLayout = LauncherAppState.getInstance()
.getDynamicGrid().getDeviceProfile().defaultLayoutId;
@@ -350,7 +390,7 @@ public class LauncherProvider extends ContentProvider {
private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
private final Context mContext;
- private final AppWidgetHost mAppWidgetHost;
+ @Thunk final AppWidgetHost mAppWidgetHost;
private long mMaxItemId = -1;
private long mMaxScreenId = -1;
@@ -421,7 +461,8 @@ public class LauncherProvider extends ContentProvider {
"modified INTEGER NOT NULL DEFAULT 0," +
"restored INTEGER NOT NULL DEFAULT 0," +
"profileId INTEGER DEFAULT " + userSerialNumber + "," +
- "rank INTEGER NOT NULL DEFAULT 0" +
+ "rank INTEGER NOT NULL DEFAULT 0," +
+ "options INTEGER NOT NULL DEFAULT 0" +
");");
addWorkspacesTable(db);
@@ -478,142 +519,116 @@ public class LauncherProvider extends ContentProvider {
private void setFlagJustLoadedOldDb() {
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sp.edit();
- editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
- editor.putBoolean(EMPTY_DATABASE_CREATED, false);
- editor.commit();
+ sp.edit().putBoolean(EMPTY_DATABASE_CREATED, false).commit();
}
private void setFlagEmptyDbCreated() {
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sp.edit();
- editor.putBoolean(EMPTY_DATABASE_CREATED, true);
- editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
- editor.commit();
+ sp.edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
-
- int version = oldVersion;
- if (version < MIN_DATABASE_VERSION) {
- // The version cannot be lower that this, as Launcher3 never supported a lower
+ switch (oldVersion) {
+ // The version cannot be lower that 12, as Launcher3 never supported a lower
// version of the DB.
- createEmptyDB(db);
- version = DATABASE_VERSION;
- }
-
- if (version < 13) {
- // With the new shrink-wrapped and re-orderable workspaces, it makes sense
- // to persist workspace screens and their relative order.
- mMaxScreenId = 0;
-
- addWorkspacesTable(db);
- version = 13;
- }
-
- if (version < 14) {
- db.beginTransaction();
- try {
- // Insert new column for holding widget provider name
- db.execSQL("ALTER TABLE favorites " +
- "ADD COLUMN appWidgetProvider TEXT;");
- db.setTransactionSuccessful();
- version = 14;
- } catch (SQLException ex) {
- // Old version remains, which means we wipe old data
- Log.e(TAG, ex.getMessage(), ex);
- } finally {
- db.endTransaction();
+ case 12: {
+ // With the new shrink-wrapped and re-orderable workspaces, it makes sense
+ // to persist workspace screens and their relative order.
+ mMaxScreenId = 0;
+ addWorkspacesTable(db);
}
- }
-
- if (version < 15) {
- db.beginTransaction();
- try {
- // Insert new column for holding update timestamp
- db.execSQL("ALTER TABLE favorites " +
- "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
- db.execSQL("ALTER TABLE workspaceScreens " +
- "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
- db.setTransactionSuccessful();
- version = 15;
- } catch (SQLException ex) {
- // Old version remains, which means we wipe old data
- Log.e(TAG, ex.getMessage(), ex);
- } finally {
- db.endTransaction();
+ case 13: {
+ db.beginTransaction();
+ try {
+ // Insert new column for holding widget provider name
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN appWidgetProvider TEXT;");
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ // Old version remains, which means we wipe old data
+ break;
+ } finally {
+ db.endTransaction();
+ }
}
- }
-
-
- if (version < 16) {
- db.beginTransaction();
- try {
- // Insert new column for holding restore status
- db.execSQL("ALTER TABLE favorites " +
- "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
- db.setTransactionSuccessful();
- version = 16;
- } catch (SQLException ex) {
- // Old version remains, which means we wipe old data
- Log.e(TAG, ex.getMessage(), ex);
- } finally {
- db.endTransaction();
+ case 14: {
+ db.beginTransaction();
+ try {
+ // Insert new column for holding update timestamp
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL("ALTER TABLE workspaceScreens " +
+ "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ // Old version remains, which means we wipe old data
+ break;
+ } finally {
+ db.endTransaction();
+ }
}
- }
-
- if (version < 17) {
- // We use the db version upgrade here to identify users who may not have seen
- // clings yet (because they weren't available), but for whom the clings are now
- // available (tablet users). Because one of the possible cling flows (migration)
- // is very destructive (wipes out workspaces), we want to prevent this from showing
- // until clear data. We do so by marking that the clings have been shown.
- LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
- version = 17;
- }
-
- if (version < 18) {
- // No-op
- version = 18;
- }
-
- if (version < 19) {
- // Due to a data loss bug, some users may have items associated with screen ids
- // which no longer exist. Since this can cause other problems, and since the user
- // will never see these items anyway, we use database upgrade as an opportunity to
- // clean things up.
- removeOrphanedItems(db);
- version = 19;
- }
-
- if (version < 20) {
- // Add userId column
- if (addProfileColumn(db)) {
- version = 20;
+ case 15: {
+ if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
}
- // else old version remains, which means we wipe old data
- }
-
- if (version < 21) {
- if (updateFolderItemsRank(db, true)) {
- version = 21;
+ case 16: {
+ // We use the db version upgrade here to identify users who may not have seen
+ // clings yet (because they weren't available), but for whom the clings are now
+ // available (tablet users). Because one of the possible cling flows (migration)
+ // is very destructive (wipes out workspaces), we want to prevent this from showing
+ // until clear data. We do so by marking that the clings have been shown.
+ LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
}
- }
-
- if (version == 21) {
- // Recreate workspace table with screen id a primary key
- if (recreateWorkspaceTable(db)) {
- version = 22;
+ case 17: {
+ // No-op
+ }
+ case 18: {
+ // Due to a data loss bug, some users may have items associated with screen ids
+ // which no longer exist. Since this can cause other problems, and since the user
+ // will never see these items anyway, we use database upgrade as an opportunity to
+ // clean things up.
+ removeOrphanedItems(db);
+ }
+ case 19: {
+ // Add userId column
+ if (!addProfileColumn(db)) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 20:
+ if (!updateFolderItemsRank(db, true)) {
+ break;
+ }
+ case 21:
+ // Recreate workspace table with screen id a primary key
+ if (!recreateWorkspaceTable(db)) {
+ break;
+ }
+ case 22: {
+ if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 23:
+ convertShortcutsToLauncherActivities(db);
+ case 24: {
+ // DB Upgraded successfully
+ return;
}
}
- if (version != DATABASE_VERSION) {
- Log.w(TAG, "Destroying all old data.");
- createEmptyDB(db);
- }
+ // DB was not upgraded
+ Log.w(TAG, "Destroying all old data.");
+ createEmptyDB(db);
}
@Override
@@ -624,7 +639,6 @@ public class LauncherProvider extends ContentProvider {
createEmptyDB(db);
}
-
/**
* Clears all the data for a fresh start.
*/
@@ -635,6 +649,63 @@ public class LauncherProvider extends ContentProvider {
}
/**
+ * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
+ * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
+ */
+ private void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
+ db.beginTransaction();
+ Cursor c = null;
+ SQLiteStatement updateStmt = null;
+
+ try {
+ // Only consider the primary user as other users can't have a shortcut.
+ long userSerial = UserManagerCompat.getInstance(mContext)
+ .getSerialNumberForUser(UserHandleCompat.myUserHandle());
+ c = db.query(TABLE_FAVORITES, new String[] {
+ Favorites._ID,
+ Favorites.INTENT,
+ }, "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + " AND profileId=" + userSerial,
+ null, null, null, null);
+
+ updateStmt = db.compileStatement("UPDATE favorites SET itemType="
+ + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?");
+
+ final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
+ final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
+
+ while (c.moveToNext()) {
+ String intentDescription = c.getString(intentIndex);
+ Intent intent;
+ try {
+ intent = Intent.parseUri(intentDescription, 0);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Unable to parse intent", e);
+ continue;
+ }
+
+ if (!InstallShortcutReceiver.isLauncherActivity(intent, mContext)) {
+ continue;
+ }
+
+ long id = c.getLong(idIndex);
+ updateStmt.bindLong(1, id);
+ updateStmt.execute();
+ }
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ Log.w(TAG, "Error deduping shortcuts", ex);
+ } finally {
+ db.endTransaction();
+ if (c != null) {
+ c.close();
+ }
+ if (updateStmt != null) {
+ updateStmt.close();
+ }
+ }
+ }
+
+ /**
* Recreates workspace table and migrates data to the new table.
*/
public boolean recreateWorkspaceTable(SQLiteDatabase db) {
@@ -682,7 +753,7 @@ public class LauncherProvider extends ContentProvider {
return true;
}
- private boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
+ @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
db.beginTransaction();
try {
if (addRankColumn) {
@@ -715,20 +786,21 @@ public class LauncherProvider extends ContentProvider {
}
private boolean addProfileColumn(SQLiteDatabase db) {
+ UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+ // Default to the serial number of this user, for older
+ // shortcuts.
+ long userSerialNumber = userManager.getSerialNumberForUser(
+ UserHandleCompat.myUserHandle());
+ return addIntegerColumn(db, Favorites.PROFILE_ID, userSerialNumber);
+ }
+
+ private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
db.beginTransaction();
try {
- UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
- // Default to the serial number of this user, for older
- // shortcuts.
- long userSerialNumber = userManager.getSerialNumberForUser(
- UserHandleCompat.myUserHandle());
- // Insert new column for holding user serial number
- db.execSQL("ALTER TABLE favorites " +
- "ADD COLUMN profileId INTEGER DEFAULT "
- + userSerialNumber + ";");
+ db.execSQL("ALTER TABLE favorites ADD COLUMN "
+ + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
db.setTransactionSuccessful();
} catch (SQLException ex) {
- // Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
return false;
} finally {
@@ -770,23 +842,7 @@ public class LauncherProvider extends ContentProvider {
}
private long initializeMaxItemId(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 item id");
- }
-
- return id;
+ return getMaxId(db, TABLE_FAVORITES);
}
// Generates a new ID to use for an workspace screen in your database. This method
@@ -804,35 +860,11 @@ public class LauncherProvider extends ContentProvider {
return mMaxScreenId;
}
- public void updateMaxScreenId(long maxScreenId) {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
- mMaxScreenId = maxScreenId;
- }
-
private long initializeMaxScreenId(SQLiteDatabase db) {
- Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, 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 screen id");
- }
-
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
- return id;
+ return getMaxId(db, TABLE_WORKSPACE_SCREENS);
}
- private boolean initializeExternalAdd(ContentValues values) {
+ @Thunk boolean initializeExternalAdd(ContentValues values) {
// 1. Ensure that externally added items have a valid item id
long id = generateNewItemId();
values.put(LauncherSettings.Favorites._ID, id);
@@ -919,7 +951,7 @@ public class LauncherProvider extends ContentProvider {
return rank;
}
- private int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
+ @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
ArrayList<Long> screenIds = new ArrayList<Long>();
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, screenIds);
@@ -946,7 +978,7 @@ public class LauncherProvider extends ContentProvider {
return count;
}
- private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
+ @Thunk void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
final ContentResolver resolver = mContext.getContentResolver();
Cursor c = null;
int count = 0;
@@ -1242,6 +1274,27 @@ public class LauncherProvider extends ContentProvider {
}
}
+ /**
+ * @return the max _id in the provided table.
+ */
+ @Thunk static long getMaxId(SQLiteDatabase db, String table) {
+ Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
+ // get the result
+ long id = -1;
+ if (c != null && c.moveToNext()) {
+ id = c.getLong(0);
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ if (id == -1) {
+ throw new RuntimeException("Error: could not query max id in " + table);
+ }
+
+ return id;
+ }
+
static class SqlArguments {
public final String table;
public final String where;
diff --git a/src/com/android/launcher3/LauncherScroller.java b/src/com/android/launcher3/LauncherScroller.java
index 3bd0a78c4..a9b49556b 100644
--- a/src/com/android/launcher3/LauncherScroller.java
+++ b/src/com/android/launcher3/LauncherScroller.java
@@ -20,7 +20,6 @@ import android.animation.TimeInterpolator;
import android.content.Context;
import android.hardware.SensorManager;
import android.os.Build;
-import android.util.FloatMath;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -409,7 +408,7 @@ public class LauncherScroller {
float dx = (float) (mFinalX - mStartX);
float dy = (float) (mFinalY - mStartY);
- float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+ float hyp = (float) Math.hypot(dx, dy);
float ndx = dx / hyp;
float ndy = dy / hyp;
@@ -426,7 +425,7 @@ public class LauncherScroller {
mMode = FLING_MODE;
mFinished = false;
- float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+ float velocity = (float) Math.hypot(velocityX, velocityY);
mVelocity = velocity;
mDuration = getSplineFlingDuration(velocity);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 13fd7ee30..90e60e450 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -19,10 +19,12 @@ package com.android.launcher3;
import android.net.Uri;
import android.provider.BaseColumns;
+import com.android.launcher3.config.ProviderConfig;
+
/**
* Settings related utilities.
*/
-class LauncherSettings {
+public class LauncherSettings {
/** Columns required on table staht will be subject to backup and restore. */
static interface ChangeLogColumns extends BaseColumns {
/**
@@ -45,7 +47,7 @@ class LauncherSettings {
* an Intent that can be launched.
* <P>Type: TEXT</P>
*/
- static final String INTENT = "intent";
+ public static final String INTENT = "intent";
/**
* The type of the gesture
@@ -104,72 +106,59 @@ class LauncherSettings {
*
* Tracks the order of workspace screens.
*/
- static final class WorkspaceScreens implements ChangeLogColumns {
+ public static final class WorkspaceScreens implements ChangeLogColumns {
+
+ public static final String TABLE_NAME = "workspaceScreens";
+
/**
* The content:// style URL for this table
*/
static final Uri CONTENT_URI = Uri.parse("content://" +
- LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_WORKSPACE_SCREENS +
- "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+ ProviderConfig.AUTHORITY + "/" + TABLE_NAME);
/**
* The rank of this screen -- ie. how it is ordered relative to the other screens.
* <P>Type: INTEGER</P>
*/
- static final String SCREEN_RANK = "screenRank";
+ public static final String SCREEN_RANK = "screenRank";
}
/**
* 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");
+ public static final class Favorites implements BaseLauncherColumns {
- /**
- * The content:// style URL for this table
- */
- static final Uri OLD_CONTENT_URI = Uri.parse("content://" +
- LauncherProvider.OLD_AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
- "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+ public static final String TABLE_NAME = "favorites";
/**
- * The content:// style URL for this table. When this Uri is used, no notification is
- * sent if the content changes.
+ * The content:// style URL for this table
*/
- static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
- LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
- "?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
+ public static final Uri CONTENT_URI = Uri.parse("content://" +
+ ProviderConfig.AUTHORITY + "/" + TABLE_NAME);
/**
* 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);
+ static Uri getContentUri(long id) {
+ return Uri.parse("content://" + ProviderConfig.AUTHORITY +
+ "/" + TABLE_NAME + "/" + id);
}
/**
* The container holding the favorite
* <P>Type: INTEGER</P>
*/
- static final String CONTAINER = "container";
+ public 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;
+ public static final int CONTAINER_DESKTOP = -100;
+ public static final int CONTAINER_HOTSEAT = -101;
static final String containerToString(int container) {
switch (container) {
@@ -183,7 +172,7 @@ class LauncherSettings {
* The screen holding the favorite (if container is CONTAINER_DESKTOP)
* <P>Type: INTEGER</P>
*/
- static final String SCREEN = "screen";
+ public static final String SCREEN = "screen";
/**
* The X coordinate of the cell holding the favorite
@@ -236,12 +225,12 @@ class LauncherSettings {
/**
* The favorite is a widget
*/
- static final int ITEM_TYPE_APPWIDGET = 4;
+ public static final int ITEM_TYPE_APPWIDGET = 4;
/**
* The favorite is a custom widget provided by the launcher
*/
- static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
+ public static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
/**
* The favorite is a clock
@@ -309,5 +298,11 @@ class LauncherSettings {
* <p>Type: INTEGER</p>
*/
static final String RANK = "rank";
+
+ /**
+ * Stores general flag based options for {@link ItemInfo}s.
+ * <p>Type: INTEGER</p>
+ */
+ static final String OPTIONS = "options";
}
}
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
new file mode 100644
index 000000000..8ba5c60f3
--- /dev/null
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -0,0 +1,783 @@
+/*
+ * Copyright (C) 2015 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.content.res.Resources;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.WidgetsContainerView;
+
+import java.util.HashMap;
+
+/**
+ * TODO: figure out what kind of tests we can write for this
+ *
+ * Things to test when changing the following class.
+ * - 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
+ */
+public class LauncherStateTransitionAnimation {
+
+ /**
+ * Callbacks made during the state transition
+ */
+ interface Callbacks {
+ public void onStateTransitionHideSearchBar();
+ }
+
+ /**
+ * Private callbacks made during transition setup.
+ */
+ static abstract class PrivateTransitionCallbacks {
+ void onRevealViewVisible(View revealView, View contentView, View allAppsButtonView) {}
+ void onAnimationComplete(View revealView, View contentView, View allAppsButtonView) {}
+ float getMaterialRevealViewFinalAlpha(View revealView) {
+ return 0;
+ }
+ float getMaterialRevealViewFinalXDrift(View revealView) {
+ return 0;
+ }
+ float getMaterialRevealViewFinalYDrift(View revealView) {
+ return 0;
+ }
+ float getMaterialRevealViewStartFinalRadius() {
+ return 0;
+ }
+ AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
+ View allAppsButtonView) {
+ return null;
+ }
+ }
+
+ public static final String TAG = "LauncherStateTransitionAnimation";
+
+ // Flags to determine how to set the layers on views before the transition animation
+ public static final int BUILD_LAYER = 0;
+ public static final int BUILD_AND_SET_LAYER = 1;
+ public static final int SINGLE_FRAME_DELAY = 16;
+
+ @Thunk Launcher mLauncher;
+ @Thunk Callbacks mCb;
+ @Thunk AnimatorSet mStateAnimation;
+
+ public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
+ mLauncher = l;
+ mCb = cb;
+ }
+
+ /**
+ * Starts an animation to the apps view.
+ */
+ public void startAnimationToAllApps(final boolean animated) {
+ final AppsContainerView toView = mLauncher.getAppsView();
+ PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
+ private int[] mAllAppsToPanelDelta;
+
+ @Override
+ public void onRevealViewVisible(View revealView, View contentView,
+ View allAppsButtonView) {
+ toView.setBackground(null);
+ // Get the y delta between the center of the page and the center of the all apps
+ // button
+ mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
+ allAppsButtonView, null);
+ }
+ @Override
+ public float getMaterialRevealViewFinalAlpha(View revealView) {
+ return 1f;
+ }
+ @Override
+ public float getMaterialRevealViewFinalXDrift(View revealView) {
+ return mAllAppsToPanelDelta[0];
+ }
+ @Override
+ public float getMaterialRevealViewFinalYDrift(View revealView) {
+ return mAllAppsToPanelDelta[1];
+ }
+ @Override
+ public float getMaterialRevealViewStartFinalRadius() {
+ int allAppsButtonSize = LauncherAppState.getInstance().
+ getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
+ return allAppsButtonSize / 2;
+ }
+ @Override
+ public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
+ final View revealView, final View allAppsButtonView) {
+ return new AnimatorListenerAdapter() {
+ public void onAnimationStart(Animator animation) {
+ allAppsButtonView.setVisibility(View.INVISIBLE);
+ }
+ public void onAnimationEnd(Animator animation) {
+ allAppsButtonView.setVisibility(View.VISIBLE);
+ }
+ };
+ }
+ };
+ startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, toView, toView.getContentView(),
+ toView.getRevealView(), animated, false /* hideSearchBar */, cb);
+ }
+
+ /**
+ * Starts an animation to the widgets view.
+ */
+ public void startAnimationToWidgets(final boolean animated) {
+ final WidgetsContainerView toView = mLauncher.getWidgetsView();
+ final Resources res = mLauncher.getResources();
+ PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
+ @Override
+ public void onRevealViewVisible(View revealView, View contentView,
+ View allAppsButtonView) {
+ revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
+ }
+ @Override
+ public float getMaterialRevealViewFinalAlpha(View revealView) {
+ return 0.3f;
+ }
+ @Override
+ public float getMaterialRevealViewFinalYDrift(View revealView) {
+ return revealView.getMeasuredHeight() / 2;
+ }
+ };
+ startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, toView,
+ toView.getContentView(), toView.getRevealView(), animated, true /* hideSearchBar */,
+ cb);
+ }
+
+ /**
+ * Starts and animation to the workspace from the current overlay view.
+ */
+ public void startAnimationToWorkspace(final Launcher.State fromState,
+ final Workspace.State toWorkspaceState, final boolean animated,
+ final Runnable onCompleteRunnable) {
+ if (toWorkspaceState != Workspace.State.NORMAL &&
+ toWorkspaceState != Workspace.State.SPRING_LOADED &&
+ toWorkspaceState != Workspace.State.OVERVIEW) {
+ Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
+ }
+
+ if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
+ startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, animated,
+ onCompleteRunnable);
+ } else {
+ startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, animated,
+ onCompleteRunnable);
+ }
+ }
+
+ /**
+ * Creates and starts a new animation to a particular overlay view.
+ */
+ private void startAnimationToOverlay(final Workspace.State toWorkspaceState, final View toView,
+ final View contentView, final View revealView, final boolean animated,
+ final boolean hideSearchBar, final PrivateTransitionCallbacks pCb) {
+ final Resources res = mLauncher.getResources();
+ final boolean material = Utilities.isLmpOrAbove();
+ final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
+ final int itemsAlphaStagger =
+ res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
+
+ final View allAppsButtonView = mLauncher.getAllAppsButton();
+ final View fromView = mLauncher.getWorkspace();
+
+ final HashMap<View, Integer> layerViews = new HashMap<>();
+
+ // If for some reason our views aren't initialized, don't animate
+ boolean initialized = allAppsButtonView != null;
+
+ // Cancel the current animation
+ cancelAnimation();
+
+ // Create the workspace animation.
+ // NOTE: this call apparently also sets the state for the workspace if !animated
+ Animator workspaceAnim = mLauncher.getWorkspace().getChangeStateAnimation(
+ toWorkspaceState, animated, layerViews);
+
+ if (animated && initialized) {
+ mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+
+ // Setup the reveal view animation
+ int width = revealView.getMeasuredWidth();
+ int height = revealView.getMeasuredHeight();
+ float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
+ revealView.setVisibility(View.VISIBLE);
+ revealView.setAlpha(0f);
+ revealView.setTranslationY(0f);
+ revealView.setTranslationX(0f);
+ pCb.onRevealViewVisible(revealView, contentView, allAppsButtonView);
+
+ // Calculate the final animation values
+ final float revealViewToAlpha;
+ final float revealViewToXDrift;
+ final float revealViewToYDrift;
+ if (material) {
+ revealViewToAlpha = pCb.getMaterialRevealViewFinalAlpha(revealView);
+ revealViewToYDrift = pCb.getMaterialRevealViewFinalYDrift(revealView);
+ revealViewToXDrift = pCb.getMaterialRevealViewFinalXDrift(revealView);
+ } else {
+ revealViewToAlpha = 0f;
+ revealViewToYDrift = 2 * height / 3;
+ revealViewToXDrift = 0;
+ }
+
+ // Create the animators
+ PropertyValuesHolder panelAlpha =
+ PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f);
+ PropertyValuesHolder panelDriftY =
+ PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0);
+ PropertyValuesHolder panelDriftX =
+ PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0);
+ ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
+ panelAlpha, panelDriftY, panelDriftX);
+ panelAlphaAndDrift.setDuration(revealDuration);
+ panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
+ // Play the animation
+ layerViews.put(revealView, BUILD_AND_SET_LAYER);
+ mStateAnimation.play(panelAlphaAndDrift);
+
+ // Setup the animation for the content view
+ contentView.setVisibility(View.VISIBLE);
+ contentView.setAlpha(0f);
+ contentView.setTranslationY(revealViewToYDrift);
+ layerViews.put(contentView, BUILD_AND_SET_LAYER);
+
+ // Create the individual animators
+ ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
+ revealViewToYDrift, 0);
+ pageDrift.setDuration(revealDuration);
+ pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+ pageDrift.setStartDelay(itemsAlphaStagger);
+ mStateAnimation.play(pageDrift);
+
+ ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
+ itemsAlpha.setDuration(revealDuration);
+ itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+ itemsAlpha.setStartDelay(itemsAlphaStagger);
+ mStateAnimation.play(itemsAlpha);
+
+ if (material) {
+ // Animate the all apps button
+ float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
+ AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
+ revealView, allAppsButtonView);
+ Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2,
+ height / 2, startRadius, revealRadius);
+ reveal.setDuration(revealDuration);
+ reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+ if (listener != null) {
+ reveal.addListener(listener);
+ }
+ mStateAnimation.play(reveal);
+ }
+
+ mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+
+ // Hide the reveal view
+ revealView.setVisibility(View.INVISIBLE);
+ pCb.onAnimationComplete(revealView, contentView, allAppsButtonView);
+
+ // Disable all necessary layers
+ for (View v : layerViews.keySet()) {
+ if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+ v.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ if (hideSearchBar) {
+ mCb.onStateTransitionHideSearchBar();
+ }
+
+ // This can hold unnecessary references to views.
+ mStateAnimation = null;
+ }
+
+ });
+
+ // Play the workspace animation
+ if (workspaceAnim != null) {
+ mStateAnimation.play(workspaceAnim);
+ }
+
+ // Dispatch the prepare transition signal
+ dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+ dispatchOnLauncherTransitionPrepare(toView, animated, false);
+
+
+ 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;
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+
+ // Enable all necessary layers
+ for (View v : layerViews.keySet()) {
+ if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ if (Utilities.isViewAttachedToWindow(v)) {
+ v.buildLayer();
+ }
+ }
+
+ // Focus the new view
+ toView.requestFocus();
+
+ mStateAnimation.start();
+ }
+ };
+ toView.bringToFront();
+ toView.setVisibility(View.VISIBLE);
+ toView.post(startAnimRunnable);
+ } else {
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ toView.setVisibility(View.VISIBLE);
+ toView.bringToFront();
+
+ // Show the content view
+ contentView.setVisibility(View.VISIBLE);
+
+ if (hideSearchBar) {
+ mCb.onStateTransitionHideSearchBar();
+ }
+
+ dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionPrepare(toView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+ }
+ }
+
+ /**
+ * Starts and animation to the workspace from the apps view.
+ */
+ private void startAnimationToWorkspaceFromAllApps(final Launcher.State fromState,
+ final Workspace.State toWorkspaceState, final boolean animated,
+ final Runnable onCompleteRunnable) {
+ AppsContainerView appsView = mLauncher.getAppsView();
+ PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
+ int[] mAllAppsToPanelDelta;
+
+ @Override
+ public void onRevealViewVisible(View revealView, View contentView,
+ View allAppsButtonView) {
+ // Get the y delta between the center of the page and the center of the all apps
+ // button
+ mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
+ allAppsButtonView, null);
+ }
+ @Override
+ public float getMaterialRevealViewFinalXDrift(View revealView) {
+ return mAllAppsToPanelDelta[0];
+ }
+ @Override
+ public float getMaterialRevealViewFinalYDrift(View revealView) {
+ return mAllAppsToPanelDelta[1];
+ }
+ @Override
+ float getMaterialRevealViewFinalAlpha(View revealView) {
+ // No alpha anim from all apps
+ return 1f;
+ }
+ @Override
+ float getMaterialRevealViewStartFinalRadius() {
+ int allAppsButtonSize = LauncherAppState.getInstance().
+ getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
+ return allAppsButtonSize / 2;
+ }
+ @Override
+ public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
+ final View revealView, final View allAppsButtonView) {
+ return new AnimatorListenerAdapter() {
+ public void onAnimationStart(Animator animation) {
+ // We set the alpha instead of visibility to ensure that the focus does not
+ // get taken from the all apps view
+ allAppsButtonView.setVisibility(View.VISIBLE);
+ allAppsButtonView.setAlpha(0f);
+ }
+ public void onAnimationEnd(Animator animation) {
+ // Hide the reveal view
+ revealView.setVisibility(View.INVISIBLE);
+
+ // Show the all apps button, and focus it
+ allAppsButtonView.setAlpha(1f);
+ }
+ };
+ }
+ };
+ startAnimationToWorkspaceFromOverlay(toWorkspaceState, appsView, appsView.getContentView(),
+ appsView.getRevealView(), animated, onCompleteRunnable, cb);
+ }
+
+ /**
+ * Starts and animation to the workspace from the widgets view.
+ */
+ private void startAnimationToWorkspaceFromWidgets(final Launcher.State fromState,
+ final Workspace.State toWorkspaceState, final boolean animated,
+ final Runnable onCompleteRunnable) {
+ final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
+ final Resources res = mLauncher.getResources();
+ PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
+ @Override
+ public void onRevealViewVisible(View revealView, View contentView,
+ View allAppsButtonView) {
+ revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
+ }
+ @Override
+ public float getMaterialRevealViewFinalYDrift(View revealView) {
+ return revealView.getMeasuredHeight() / 2;
+ }
+ @Override
+ float getMaterialRevealViewFinalAlpha(View revealView) {
+ return 0.4f;
+ }
+ @Override
+ public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
+ final View revealView, final View allAppsButtonView) {
+ return new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ // Hide the reveal view
+ revealView.setVisibility(View.INVISIBLE);
+ }
+ };
+ }
+ };
+ startAnimationToWorkspaceFromOverlay(toWorkspaceState, widgetsView,
+ widgetsView.getContentView(), widgetsView.getRevealView(), animated,
+ onCompleteRunnable, cb);
+ }
+
+ /**
+ * Creates and starts a new animation to the workspace.
+ */
+ private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
+ final View fromView, final View contentView, final View revealView,
+ final boolean animated, final Runnable onCompleteRunnable,
+ final PrivateTransitionCallbacks pCb) {
+ final Resources res = mLauncher.getResources();
+ final boolean material = Utilities.isLmpOrAbove();
+ final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
+ final int itemsAlphaStagger =
+ res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
+
+ final View allAppsButtonView = mLauncher.getAllAppsButton();
+ final View toView = mLauncher.getWorkspace();
+
+ final HashMap<View, Integer> layerViews = new HashMap<>();
+
+ // If for some reason our views aren't initialized, don't animate
+ boolean initialized = allAppsButtonView != null;
+
+ // Cancel the current animation
+ cancelAnimation();
+
+ // Create the workspace animation.
+ // NOTE: this call apparently also sets the state for the workspace if !animated
+ Animator workspaceAnim = mLauncher.getWorkspace().getChangeStateAnimation(
+ toWorkspaceState, animated, layerViews);
+
+ if (animated && initialized) {
+ mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+
+ // Play the workspace animation
+ if (workspaceAnim != null) {
+ mStateAnimation.play(workspaceAnim);
+ }
+
+ // hideAppsCustomizeHelper is called in some cases when it is already hidden
+ // don't perform all these no-op animations. In particularly, this was causing
+ // the all-apps button to pop in and out.
+ if (fromView.getVisibility() == View.VISIBLE) {
+ int width = revealView.getMeasuredWidth();
+ int height = revealView.getMeasuredHeight();
+ float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
+ revealView.setVisibility(View.VISIBLE);
+ revealView.setAlpha(1f);
+ revealView.setTranslationY(0);
+ layerViews.put(revealView, BUILD_AND_SET_LAYER);
+ pCb.onRevealViewVisible(revealView, contentView, allAppsButtonView);
+
+ // Calculate the final animation values
+ final float revealViewToXDrift;
+ final float revealViewToYDrift;
+ if (material) {
+ revealViewToYDrift = pCb.getMaterialRevealViewFinalYDrift(revealView);
+ revealViewToXDrift = pCb.getMaterialRevealViewFinalXDrift(revealView);
+ } else {
+ revealViewToYDrift = 2 * height / 3;
+ revealViewToXDrift = 0;
+ }
+
+ // The vertical motion of the apps panel should be delayed by one frame
+ // from the conceal animation in order to give the right feel. We correspondingly
+ // shorten the duration so that the slide and conceal end at the same time.
+ TimeInterpolator decelerateInterpolator = material ?
+ new LogDecelerateInterpolator(100, 0) :
+ new DecelerateInterpolator(1f);
+ ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY",
+ 0, revealViewToYDrift);
+ panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
+ panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
+ panelDriftY.setInterpolator(decelerateInterpolator);
+ mStateAnimation.play(panelDriftY);
+
+ ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX",
+ 0, revealViewToXDrift);
+ panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
+ panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
+ panelDriftX.setInterpolator(decelerateInterpolator);
+ mStateAnimation.play(panelDriftX);
+
+ // Setup animation for the reveal panel alpha
+ final float revealViewToAlpha = !material ? 0f :
+ pCb.getMaterialRevealViewFinalAlpha(revealView);
+ if (revealViewToAlpha != 1f) {
+ ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha",
+ 1f, revealViewToAlpha);
+ panelAlpha.setDuration(material ? revealDuration : 150);
+ panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
+ panelAlpha.setInterpolator(decelerateInterpolator);
+ mStateAnimation.play(panelAlpha);
+ }
+
+ // Setup the animation for the content view
+ layerViews.put(contentView, BUILD_AND_SET_LAYER);
+
+ // Create the individual animators
+ ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(contentView, "translationY",
+ 0, revealViewToYDrift);
+ contentView.setTranslationY(0);
+ pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
+ pageDrift.setInterpolator(decelerateInterpolator);
+ pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
+ mStateAnimation.play(pageDrift);
+
+ contentView.setAlpha(1f);
+ ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(contentView, "alpha", 1f, 0f);
+ itemsAlpha.setDuration(100);
+ itemsAlpha.setInterpolator(decelerateInterpolator);
+ mStateAnimation.play(itemsAlpha);
+
+ if (material) {
+ // Animate the all apps button
+ float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
+ AnimatorListenerAdapter listener =
+ pCb.getMaterialRevealViewAnimatorListener(revealView, allAppsButtonView);
+ Animator reveal =
+ LauncherAnimUtils.createCircularReveal(revealView, width / 2,
+ height / 2, revealRadius, finalRadius);
+ reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+ reveal.setDuration(revealDuration);
+ reveal.setStartDelay(itemsAlphaStagger);
+ if (listener != null) {
+ reveal.addListener(listener);
+ }
+ mStateAnimation.play(reveal);
+ }
+
+ dispatchOnLauncherTransitionPrepare(fromView, animated, true);
+ dispatchOnLauncherTransitionPrepare(toView, animated, true);
+ }
+
+ mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ fromView.setVisibility(View.GONE);
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
+
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+
+ // Animation complete callback
+ pCb.onAnimationComplete(revealView, contentView, allAppsButtonView);
+
+ // Disable all necessary layers
+ for (View v : layerViews.keySet()) {
+ if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+ v.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ // Reset page transforms
+ if (contentView != null) {
+ contentView.setTranslationX(0);
+ contentView.setTranslationY(0);
+ contentView.setAlpha(1);
+ }
+
+ // This can hold unnecessary references to views.
+ mStateAnimation = 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;
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+
+ // Enable all necessary layers
+ for (View v : layerViews.keySet()) {
+ if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ if (Utilities.isLmpOrAbove()) {
+ v.buildLayer();
+ }
+ }
+ mStateAnimation.start();
+ }
+ };
+ fromView.post(startAnimRunnable);
+ } 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);
+
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }
+ }
+
+
+ /**
+ * Dispatches the prepare-transition event to suitable views.
+ */
+ void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
+ toWorkspace);
+ }
+ }
+
+ /**
+ * Dispatches the start-transition event to suitable views.
+ */
+ void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
+ toWorkspace);
+ }
+
+ // Update the workspace transition step as well
+ dispatchOnLauncherTransitionStep(v, 0f);
+ }
+
+ /**
+ * Dispatches the step-transition event to suitable views.
+ */
+ void dispatchOnLauncherTransitionStep(View v, float t) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
+ }
+ }
+
+ /**
+ * Dispatches the end-transition event to suitable views.
+ */
+ void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
+ toWorkspace);
+ }
+
+ // Update the workspace transition step as well
+ dispatchOnLauncherTransitionStep(v, 1f);
+ }
+
+ /**
+ * Cancels the current animation.
+ */
+ private void cancelAnimation() {
+ if (mStateAnimation != null) {
+ mStateAnimation.setDuration(0);
+ mStateAnimation.cancel();
+ mStateAnimation = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/PackageChangedReceiver.java b/src/com/android/launcher3/PackageChangedReceiver.java
deleted file mode 100644
index e59f6d81d..000000000
--- a/src/com/android/launcher3/PackageChangedReceiver.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.launcher3;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class PackageChangedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, Intent intent) {
- final String packageName = intent.getData().getSchemeSpecificPart();
-
- if (packageName == null || packageName.length() == 0) {
- // they sent us a bad intent
- return;
- }
- // in rare cases the receiver races with the application to set up LauncherAppState
- LauncherAppState.setApplicationContext(context.getApplicationContext());
- LauncherAppState app = LauncherAppState.getInstance();
- WidgetPreviewLoader.removePackageFromDb(app.getWidgetPreviewCacheDb(), packageName);
- }
-}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 7d65f4686..88295c084 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -19,6 +19,7 @@ package com.android.launcher3;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
@@ -51,6 +52,8 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
+import com.android.launcher3.util.Thunk;
+
import java.util.ArrayList;
interface Page {
@@ -124,7 +127,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
protected LauncherScroller mScroller;
private Interpolator mDefaultInterpolator;
private VelocityTracker mVelocityTracker;
- private int mPageSpacing = 0;
+ @Thunk int mPageSpacing = 0;
private float mParentDownMotionX;
private float mParentDownMotionY;
@@ -207,8 +210,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
private boolean mWasInOverscroll = false;
// Page Indicator
- private int mPageIndicatorViewId;
- private PageIndicator mPageIndicator;
+ @Thunk int mPageIndicatorViewId;
+ @Thunk PageIndicator mPageIndicator;
private boolean mAllowPagedViewAnimations = true;
// The viewport whether the pages are to be contained (the actual view may be larger than the
@@ -227,7 +230,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
protected View mDragView;
protected AnimatorSet mZoomInOutAnim;
private Runnable mSidePageHoverRunnable;
- private int mSidePageHoverIndex = -1;
+ @Thunk int mSidePageHoverIndex = -1;
// This variable's scope is only for the duration of startReordering() and endReordering()
private boolean mReorderingStarted = false;
// This variable's scope is for the duration of startReordering() and after the zoomIn()
@@ -246,14 +249,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
private Rect mAltTmpRect = new Rect();
// Fling to delete
- private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
+ @Thunk int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
private float FLING_TO_DELETE_FRICTION = 0.035f;
// The degrees specifies how much deviation from the up vector to still consider a fling "up"
private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
protected int mFlingToDeleteThresholdVelocity = -1400;
// Drag to delete
- private boolean mDeferringForDelete = false;
- private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
+ @Thunk boolean mDeferringForDelete = false;
+ @Thunk int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
// Drop to delete
@@ -473,13 +476,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
/**
* Returns the index of the currently displayed page.
- *
- * @return The index of the currently displayed page.
*/
int getCurrentPage() {
return mCurrentPage;
}
+ /**
+ * Returns the index of page to be shown immediately afterwards.
+ */
int getNextPage() {
return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
}
@@ -959,8 +963,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
LayoutParams nextLp;
int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
- if (mPageScrolls == null || getChildCount() != mChildCountOnLastLayout) {
- mPageScrolls = new int[getChildCount()];
+ if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
+ mPageScrolls = new int[childCount];
}
for (int i = startIndex; i != endIndex; i += delta) {
@@ -1007,19 +1011,36 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
}
}
- if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < childCount) {
updateCurrentPageScroll();
mFirstLayout = false;
}
- if (childCount > 0) {
- final int index = isLayoutRtl() ? 0 : childCount - 1;
- mMaxScrollX = getScrollForPage(index);
+ final LayoutTransition transition = getLayoutTransition();
+ // If the transition is running defer updating max scroll, as some empty pages could
+ // still be present, and a max scroll change could cause sudden jumps in scroll.
+ if (transition != null && transition.isRunning()) {
+ transition.addTransitionListener(new LayoutTransition.TransitionListener() {
+
+ @Override
+ public void startTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) { }
+
+ @Override
+ public void endTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) {
+ // Wait until all transitions are complete.
+ if (!transition.isRunning()) {
+ transition.removeTransitionListener(this);
+ updateMaxScrollX();
+ }
+ }
+ });
} else {
- mMaxScrollX = 0;
+ updateMaxScrollX();
}
- if (mScroller.isFinished() && mChildCountOnLastLayout != getChildCount() &&
+ if (mScroller.isFinished() && mChildCountOnLastLayout != childCount &&
!mDeferringForDelete) {
if (mRestorePage != INVALID_RESTORE_PAGE) {
setCurrentPage(mRestorePage);
@@ -1028,13 +1049,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
setCurrentPage(getNextPage());
}
}
- mChildCountOnLastLayout = getChildCount();
+ mChildCountOnLastLayout = childCount;
if (isReordering(true)) {
updateDragViewTranslationDuringDrag();
}
}
+ private void updateMaxScrollX() {
+ int childCount = getChildCount();
+ if (childCount > 0) {
+ final int index = isLayoutRtl() ? 0 : childCount - 1;
+ mMaxScrollX = getScrollForPage(index);
+ } else {
+ mMaxScrollX = 0;
+ }
+ }
+
public void setPageSpacing(int pageSpacing) {
mPageSpacing = pageSpacing;
requestLayout();
@@ -1700,7 +1731,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
setEnableOverscroll(!freeScroll);
}
- private void setEnableOverscroll(boolean enable) {
+ protected void setEnableOverscroll(boolean enable) {
mAllowOverScroll = enable;
}
@@ -2356,7 +2387,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
super(superState);
}
- private SavedState(Parcel in) {
+ @Thunk SavedState(Parcel in) {
super(in);
currentPage = in.readInt();
}
@@ -2514,7 +2545,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
invalidate();
}
- private void onPostReorderingAnimationCompleted() {
+ @Thunk void onPostReorderingAnimationCompleted() {
// Trigger the callback when reordering has settled
--mPostReorderingPreZoomInRemainingAnimationCount;
if (mPostReorderingPreZoomInRunnable != null &&
diff --git a/src/com/android/launcher3/PagedViewCellLayout.java b/src/com/android/launcher3/PagedViewCellLayout.java
deleted file mode 100644
index 2d9e10b9d..000000000
--- a/src/com/android/launcher3/PagedViewCellLayout.java
+++ /dev/null
@@ -1,492 +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.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-
-/**
- * An abstraction of the original CellLayout which supports laying out items
- * which span multiple cells into a grid-like layout. Also supports dimming
- * to give a preview of its contents.
- */
-public class PagedViewCellLayout extends ViewGroup 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;
- 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
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- mOriginalCellWidth = mCellWidth = grid.cellWidthPx;
- mOriginalCellHeight = mCellHeight = grid.cellHeightPx;
- mCellCountX = (int) grid.numColumns;
- mCellCountY = (int) grid.numRows;
- mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
-
- mChildren = new PagedViewCellLayoutChildren(context);
- mChildren.setCellDimensions(mCellWidth, mCellHeight);
- mChildren.setGap(mWidthGap, mHeightGap);
-
- addView(mChildren);
- }
-
- public int getCellWidth() {
- return mCellWidth;
- }
-
- public int getCellHeight() {
- return mCellHeight;
- }
-
- @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();
- setLayerType(LAYER_TYPE_NONE, null);
- }
-
- @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 = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0;
- mHeightGap = 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(Context context,
- 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 (LauncherAppState.getInstance().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 + ")";
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/launcher3/PagedViewCellLayoutChildren.java b/src/com/android/launcher3/PagedViewCellLayoutChildren.java
deleted file mode 100644
index 84d2b1dd3..000000000
--- a/src/com/android/launcher3/PagedViewCellLayoutChildren.java
+++ /dev/null
@@ -1,161 +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.launcher3;
-
-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(getContext(),
- 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/launcher3/PagedViewWithDraggableItems.java b/src/com/android/launcher3/PagedViewWithDraggableItems.java
deleted file mode 100644
index 0e593698d..000000000
--- a/src/com/android/launcher3/PagedViewWithDraggableItems.java
+++ /dev/null
@@ -1,174 +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.launcher3;
-
-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);
- }
-
- public void trimMemory() {
- mLastTouchedItem = null;
- }
-
- @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.isAllAppsVisible() ||
- 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();
- }
-}
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index ac54a262f..1aaf85bbd 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -16,93 +16,17 @@
package com.android.launcher3;
-import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
-import android.os.Bundle;
-import android.os.Parcelable;
/**
- * We pass this object with a drag from the customization tray
+ * Meta data that is used for deferred binding.
+ * e.g., this object is used to pass information on dragable targets when they are dropped onto
+ * the workspace from another container.
*/
-class PendingAddItemInfo extends ItemInfo {
+public 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;
- LauncherAppWidgetProviderInfo info;
- AppWidgetHostView boundWidget;
- Bundle bindOptions = null;
-
- public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i, Parcelable data) {
- if (i.isCustomWidget) {
- itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
- } else {
- 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;
-
- spanX = i.spanX;
- spanY = i.spanY;
- minSpanX = i.minSpanX;
- minSpanY = i.minSpanY;
- }
-
- public boolean isCustomWidget() {
- return itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
- }
-
- // 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;
- componentName = copy.componentName;
- itemType = copy.itemType;
- spanX = copy.spanX;
- spanY = copy.spanY;
- minSpanX = copy.minSpanX;
- minSpanY = copy.minSpanY;
- bindOptions = copy.bindOptions == null ? null : (Bundle) copy.bindOptions.clone();
- }
-
- @Override
- public String toString() {
- return "Widget: " + componentName.toShortString();
- }
+ public ComponentName componentName;
}
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 99c2e0859..a8dcd0f06 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -44,11 +44,14 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
private boolean mIsSearchBarHidden;
private View mQSBSearchBar;
private View mDropTargetBar;
- private ButtonDropTarget mInfoDropTarget;
- private ButtonDropTarget mDeleteDropTarget;
private int mBarHeight;
private boolean mDeferOnDragEnd = false;
+ // Drop targets
+ private ButtonDropTarget mInfoDropTarget;
+ private ButtonDropTarget mDeleteDropTarget;
+ private ButtonDropTarget mUninstallDropTarget;
+
private boolean mEnableDropDownDropTargets;
public SearchDropTargetBar(Context context, AttributeSet attrs) {
@@ -61,13 +64,19 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
public void setup(Launcher launcher, DragController dragController) {
dragController.addDragListener(this);
+ dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+
dragController.addDragListener(mInfoDropTarget);
dragController.addDragListener(mDeleteDropTarget);
+ dragController.addDragListener(mUninstallDropTarget);
+
dragController.addDropTarget(mInfoDropTarget);
dragController.addDropTarget(mDeleteDropTarget);
- dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+ dragController.addDropTarget(mUninstallDropTarget);
+
mInfoDropTarget.setLauncher(launcher);
mDeleteDropTarget.setLauncher(launcher);
+ mUninstallDropTarget.setLauncher(launcher);
}
public void setQsbSearchBar(View qsb) {
@@ -116,9 +125,11 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
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);
+ mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.uninstall_target_text);
mInfoDropTarget.setSearchDropTargetBar(this);
mDeleteDropTarget.setSearchDropTargetBar(this);
+ mUninstallDropTarget.setSearchDropTargetBar(this);
mEnableDropDownDropTargets =
getResources().getBoolean(R.bool.config_useDropTargetDownTransition);
@@ -197,6 +208,10 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
*/
@Override
public void onDragStart(DragSource source, Object info, int dragAction) {
+ showDeleteTarget();
+ }
+
+ public void showDeleteTarget() {
// Animate out the QSB search bar, and animate in the drop target bar
prepareStartAnimation(mDropTargetBar);
mDropTargetBarAnim.start();
@@ -206,6 +221,16 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
}
}
+ public void hideDeleteTarget() {
+ // Restore the QSB search bar, and animate out the drop target bar
+ prepareStartAnimation(mDropTargetBar);
+ mDropTargetBarAnim.reverse();
+ if (!mIsSearchBarHidden) {
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.reverse();
+ }
+ }
+
public void deferOnDragEnd() {
mDeferOnDragEnd = true;
}
@@ -213,13 +238,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
@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();
- }
+ hideDeleteTarget();
} else {
mDeferOnDragEnd = false;
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index ff0604540..15b617683 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -177,7 +177,7 @@ public class ShortcutAndWidgetContainer extends ViewGroup {
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
- private boolean invertLayoutHorizontally() {
+ public boolean invertLayoutHorizontally() {
return mInvertIfRtl && isLayoutRtl();
}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 01f79314e..5bef845bb 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -23,7 +23,10 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.util.Log;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -46,18 +49,24 @@ public class ShortcutInfo extends ItemInfo {
* be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout
* parsing.
*/
- public static final int FLAG_AUTOINTALL_ICON = 2;
+ public static final int FLAG_AUTOINTALL_ICON = 2; //0B10;
/**
* The icon is being installed. If {@link FLAG_RESTORED_ICON} or {@link FLAG_AUTOINTALL_ICON}
* is set, then the icon is either being installed or is in a broken state.
*/
- public static final int FLAG_INSTALL_SESSION_ACTIVE = 4;
+ public static final int FLAG_INSTALL_SESSION_ACTIVE = 4; // 0B100;
/**
* Indicates that the widget restore has started.
*/
- public static final int FLAG_RESTORE_STARTED = 8;
+ public static final int FLAG_RESTORE_STARTED = 8; //0B1000;
+
+ /**
+ * Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
+ * Upto 15 different types supported.
+ */
+ public static final int FLAG_RESTORED_APP_TYPE = 0B0011110000;
/**
* The intent used to start the application.
@@ -77,6 +86,11 @@ public class ShortcutInfo extends ItemInfo {
boolean usingFallbackIcon;
/**
+ * Indicates whether we're using a low res icon
+ */
+ boolean usingLowResIcon;
+
+ /**
* If isShortcut=true and customIcon=false, this contains a reference to the
* shortcut icon as an application's resource.
*/
@@ -184,8 +198,10 @@ public class ShortcutInfo extends ItemInfo {
}
public void updateIcon(IconCache iconCache) {
- mIcon = iconCache.getIcon(promisedIntent != null ? promisedIntent : intent, user);
- usingFallbackIcon = iconCache.isDefaultIcon(mIcon, user);
+ if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
+ shouldUseLowResIcon());
+ }
}
@Override
@@ -207,9 +223,9 @@ public class ShortcutInfo extends ItemInfo {
if (!usingFallbackIcon) {
writeBitmap(values, mIcon);
}
- values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
- LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
if (iconResource != null) {
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
iconResource.packageName);
values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
@@ -256,5 +272,23 @@ public class ShortcutInfo extends ItemInfo {
mInstallProgress = progress;
status |= FLAG_INSTALL_SESSION_ACTIVE;
}
+
+ public boolean shouldUseLowResIcon() {
+ return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
+ }
+
+ public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
+ final ShortcutInfo shortcut = new ShortcutInfo();
+ shortcut.user = info.getUser();
+ shortcut.title = info.getLabel().toString();
+ shortcut.contentDescription = UserManagerCompat.getInstance(context)
+ .getBadgedLabelForUser(info.getLabel(), info.getUser());
+ shortcut.customIcon = false;
+ shortcut.intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
+ shortcut.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ shortcut.flags = AppInfo.initFlags(info);
+ shortcut.firstInstallTime = info.getFirstInstallTime();
+ return shortcut;
+ }
}
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
index a87986562..9d06f755f 100644
--- a/src/com/android/launcher3/Stats.java
+++ b/src/com/android/launcher3/Stats.java
@@ -22,14 +22,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
-import java.io.*;
-import java.util.ArrayList;
-
public class Stats {
private static final boolean DEBUG_BROADCASTS = false;
- private static final String TAG = "Launcher3/Stats";
-
- private static final boolean LOCAL_LAUNCH_LOG = true;
public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
public static final String EXTRA_INTENT = "intent";
@@ -38,54 +32,20 @@ public class Stats {
public static final String EXTRA_CELLX = "cellX";
public static final String EXTRA_CELLY = "cellY";
- private static final int LOG_VERSION = 1;
- private static final int LOG_TAG_VERSION = 0x1;
- private static final int LOG_TAG_LAUNCH = 0x1000;
-
- private static final int STATS_VERSION = 1;
- private static final int INITIAL_STATS_SIZE = 100;
-
- // TODO: delayed/batched writes
- private static final boolean FLUSH_IMMEDIATELY = true;
-
private final Launcher mLauncher;
-
private final String mLaunchBroadcastPermission;
- DataOutputStream mLog;
-
- ArrayList<String> mIntents;
- ArrayList<Integer> mHistogram;
-
public Stats(Launcher launcher) {
mLauncher = launcher;
-
mLaunchBroadcastPermission =
launcher.getResources().getString(R.string.receive_launch_broadcasts_permission);
- loadStats();
-
- if (LOCAL_LAUNCH_LOG) {
- try {
- mLog = new DataOutputStream(mLauncher.openFileOutput(
- LauncherFiles.LAUNCHES_LOG, Context.MODE_APPEND));
- mLog.writeInt(LOG_TAG_VERSION);
- mLog.writeInt(LOG_VERSION);
- } catch (FileNotFoundException e) {
- Log.e(TAG, "unable to create stats log: " + e);
- mLog = null;
- } catch (IOException e) {
- Log.e(TAG, "unable to write to stats log: " + e);
- mLog = null;
- }
- }
-
if (DEBUG_BROADCASTS) {
launcher.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- android.util.Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
+ Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
+ intent.getStringExtra(EXTRA_INTENT));
}
},
@@ -96,16 +56,6 @@ public class Stats {
}
}
- public void incrementLaunch(String intentStr) {
- int pos = mIntents.indexOf(intentStr);
- if (pos < 0) {
- mIntents.add(intentStr);
- mHistogram.add(1);
- } else {
- mHistogram.set(pos, mHistogram.get(pos) + 1);
- }
- }
-
public void recordLaunch(Intent intent) {
recordLaunch(intent, null);
}
@@ -115,7 +65,6 @@ public class Stats {
intent.setSourceBounds(null);
final String flat = intent.toUri(0);
-
Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
if (shortcut != null) {
broadcastIntent.putExtra(EXTRA_CONTAINER, shortcut.container)
@@ -124,94 +73,5 @@ public class Stats {
.putExtra(EXTRA_CELLY, shortcut.cellY);
}
mLauncher.sendBroadcast(broadcastIntent, mLaunchBroadcastPermission);
-
- incrementLaunch(flat);
-
- if (FLUSH_IMMEDIATELY) {
- saveStats();
- }
-
- if (LOCAL_LAUNCH_LOG && mLog != null) {
- try {
- mLog.writeInt(LOG_TAG_LAUNCH);
- mLog.writeLong(System.currentTimeMillis());
- if (shortcut == null) {
- mLog.writeShort(0);
- mLog.writeShort(0);
- mLog.writeShort(0);
- mLog.writeShort(0);
- } else {
- mLog.writeShort((short) shortcut.container);
- mLog.writeShort((short) shortcut.screenId);
- mLog.writeShort((short) shortcut.cellX);
- mLog.writeShort((short) shortcut.cellY);
- }
- mLog.writeUTF(flat);
- if (FLUSH_IMMEDIATELY) {
- mLog.flush(); // TODO: delayed writes
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
- private void saveStats() {
- DataOutputStream stats = null;
- try {
- stats = new DataOutputStream(mLauncher.openFileOutput(
- LauncherFiles.STATS_LOG + ".tmp", Context.MODE_PRIVATE));
- stats.writeInt(STATS_VERSION);
- final int N = mHistogram.size();
- stats.writeInt(N);
- for (int i=0; i<N; i++) {
- stats.writeUTF(mIntents.get(i));
- stats.writeInt(mHistogram.get(i));
- }
- stats.close();
- stats = null;
- mLauncher.getFileStreamPath(LauncherFiles.STATS_LOG + ".tmp")
- .renameTo(mLauncher.getFileStreamPath(LauncherFiles.STATS_LOG));
- } catch (FileNotFoundException e) {
- Log.e(TAG, "unable to create stats data: " + e);
- } catch (IOException e) {
- Log.e(TAG, "unable to write to stats data: " + e);
- } finally {
- if (stats != null) {
- try {
- stats.close();
- } catch (IOException e) { }
- }
- }
- }
-
- private void loadStats() {
- mIntents = new ArrayList<String>(INITIAL_STATS_SIZE);
- mHistogram = new ArrayList<Integer>(INITIAL_STATS_SIZE);
- DataInputStream stats = null;
- try {
- stats = new DataInputStream(mLauncher.openFileInput(LauncherFiles.STATS_LOG));
- final int version = stats.readInt();
- if (version == STATS_VERSION) {
- final int N = stats.readInt();
- for (int i=0; i<N; i++) {
- final String pkg = stats.readUTF();
- final int count = stats.readInt();
- mIntents.add(pkg);
- mHistogram.add(count);
- }
- }
- } catch (FileNotFoundException e) {
- // not a problem
- } catch (IOException e) {
- // more of a problem
-
- } finally {
- if (stats != null) {
- try {
- stats.close();
- } catch (IOException e) { }
- }
- }
}
}
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
new file mode 100644
index 000000000..4a7fffeb2
--- /dev/null
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -0,0 +1,122 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.AttributeSet;
+import android.util.Pair;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.Thunk;
+
+public class UninstallDropTarget extends ButtonDropTarget {
+
+ public UninstallDropTarget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Get the hover color
+ mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
+
+ setDrawable(R.drawable.uninstall_target_selector);
+ }
+
+ @Override
+ protected boolean supportsDrop(DragSource source, Object info) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ UserManager userManager = (UserManager)
+ getContext().getSystemService(Context.USER_SERVICE);
+ Bundle restrictions = userManager.getUserRestrictions();
+ if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+ || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
+ return false;
+ }
+ }
+
+ Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
+ return componentInfo != null && (componentInfo.second & AppInfo.DOWNLOADED_FLAG) != 0;
+ }
+
+ /**
+ * @return the component name and flags if {@param info} is an AppInfo or an app shortcut.
+ */
+ private static Pair<ComponentName, Integer> getAppInfoFlags(Object item) {
+ if (item instanceof AppInfo) {
+ AppInfo info = (AppInfo) item;
+ return Pair.create(info.componentName, info.flags);
+ } else if (item instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) item;
+ ComponentName component = info.getTargetComponent();
+ if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION
+ && component != null) {
+ return Pair.create(component, info.flags);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onDrop(DragObject d) {
+ // Differ item deletion
+ if (d.dragSource instanceof UninstallSource) {
+ ((UninstallSource) d.dragSource).deferCompleteDropAfterUninstallActivity();
+ }
+ super.onDrop(d);
+ }
+
+ @Override
+ void completeDrop(final DragObject d) {
+ final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo);
+ final UserHandleCompat user = ((ItemInfo) d.dragInfo).user;
+ if (mLauncher.startApplicationUninstallActivity(
+ componentInfo.first, componentInfo.second, user)) {
+
+ final Runnable checkIfUninstallWasSuccess = new Runnable() {
+ @Override
+ public void run() {
+ String packageName = componentInfo.first.getPackageName();
+ boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
+ getContext(), packageName, user);
+ sendUninstallResult(d.dragSource, uninstallSuccessful);
+ }
+ };
+ mLauncher.addOnResumeCallback(checkIfUninstallWasSuccess);
+ } else {
+ sendUninstallResult(d.dragSource, false);
+ }
+ }
+
+ @Thunk void sendUninstallResult(DragSource target, boolean result) {
+ if (target instanceof UninstallSource) {
+ ((UninstallSource) target).onUninstallActivityReturned(result);
+ }
+ }
+
+ /**
+ * Interface defining an object that can provide uninstallable drag objects.
+ */
+ public static interface UninstallSource {
+
+ /**
+ * A pending uninstall operation was complete.
+ * @param result true if uninstall was successful, false otherwise.
+ */
+ void onUninstallActivityReturned(boolean result);
+
+ /**
+ * Indicates that an uninstall request are made and the actual result may come
+ * after some time.
+ */
+ void deferCompleteDropAfterUninstallActivity();
+ }
+}
diff --git a/src/com/android/launcher3/UninstallShortcutReceiver.java b/src/com/android/launcher3/UninstallShortcutReceiver.java
deleted file mode 100644
index c9d0bb5f5..000000000
--- a/src/com/android/launcher3/UninstallShortcutReceiver.java
+++ /dev/null
@@ -1,133 +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.launcher3;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.widget.Toast;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-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<PendingUninstallShortcutInfo> mUninstallQueue =
- new ArrayList<PendingUninstallShortcutInfo>();
-
- // 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<PendingUninstallShortcutInfo> iter = mUninstallQueue.iterator();
- while (iter.hasNext()) {
- processUninstallShortcut(context, iter.next());
- iter.remove();
- }
- }
-
- private static void processUninstallShortcut(Context context,
- PendingUninstallShortcutInfo pendingInfo) {
- final Intent data = pendingInfo.data;
-
- LauncherAppState.setApplicationContext(context.getApplicationContext());
- LauncherAppState app = LauncherAppState.getInstance();
- synchronized (app) { // TODO: make removeShortcut internally threadsafe
- removeShortcut(context, data);
- }
- }
-
- private static void removeShortcut(Context context, Intent data) {
- 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 {
- String intentStr = c.getString(intentIndex);
- if (intentStr != null
- && intent.filterEquals(Intent.parseUri(intentStr, 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();
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 1a9b9a16c..22677c8ea 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -31,7 +31,9 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.database.Cursor;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -48,6 +50,8 @@ import android.util.SparseArray;
import android.view.View;
import android.widget.Toast;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
@@ -112,6 +116,15 @@ public final class Utilities {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
+ static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
+ byte[] data = c.getBlob(iconIndex);
+ try {
+ return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
/**
* Returns a bitmap suitable for the all apps view. If the package or the resource do not
* exist, it returns null.
@@ -544,6 +557,25 @@ public final class Utilities {
return defaultWidgetForSearchPackage;
}
+ /**
+ * Compresses the bitmap to a byte array for serialization.
+ */
+ public 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(TAG, "Could not write bitmap");
+ return null;
+ }
+ }
+
public static final Comparator<ItemInfo> RANK_COMPARATOR = new Comparator<ItemInfo>() {
@Override
diff --git a/src/com/android/launcher3/WeightWatcher.java b/src/com/android/launcher3/WeightWatcher.java
index 70b8afea8..75684797f 100644
--- a/src/com/android/launcher3/WeightWatcher.java
+++ b/src/com/android/launcher3/WeightWatcher.java
@@ -34,6 +34,8 @@ import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.launcher3.util.Thunk;
+
public class WeightWatcher extends LinearLayout {
private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
@@ -81,7 +83,7 @@ public class WeightWatcher extends LinearLayout {
}
}
};
- private MemoryTracker mMemoryService;
+ @Thunk MemoryTracker mMemoryService;
public WeightWatcher(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -134,7 +136,7 @@ public class WeightWatcher extends LinearLayout {
GraphView mRamGraph;
TextView mText;
int mPid;
- private MemoryTracker.ProcessMemInfo mMemInfo;
+ @Thunk MemoryTracker.ProcessMemInfo mMemInfo;
public ProcessWatcher(Context context) {
this(context, null);
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index d963f2db9..5c3ed9272 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -1,524 +1,361 @@
package com.android.launcher3;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
-import android.database.sqlite.SQLiteCantOpenDatabaseException;
+import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteReadOnlyDatabaseException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Shader;
+import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.Build;
import android.util.Log;
+import android.util.LongSparseArray;
+
import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.WidgetCell;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
public class WidgetPreviewLoader {
- private static abstract class SoftReferenceThreadLocal<T> {
- private ThreadLocal<SoftReference<T>> mThreadLocal;
- public SoftReferenceThreadLocal() {
- mThreadLocal = new ThreadLocal<SoftReference<T>>();
- }
-
- abstract T initialValue();
-
- public void set(T t) {
- mThreadLocal.set(new SoftReference<T>(t));
- }
-
- public T get() {
- SoftReference<T> reference = mThreadLocal.get();
- T obj;
- if (reference == null) {
- obj = initialValue();
- mThreadLocal.set(new SoftReference<T>(obj));
- return obj;
- } else {
- obj = reference.get();
- if (obj == null) {
- obj = initialValue();
- mThreadLocal.set(new SoftReference<T>(obj));
- }
- return obj;
- }
- }
- }
-
- private static class CanvasCache extends SoftReferenceThreadLocal<Canvas> {
- @Override
- protected Canvas initialValue() {
- return new Canvas();
- }
- }
-
- private static class PaintCache extends SoftReferenceThreadLocal<Paint> {
- @Override
- protected Paint initialValue() {
- return null;
- }
- }
-
- private static class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
- @Override
- protected Bitmap initialValue() {
- return null;
- }
- }
-
- private static class RectCache extends SoftReferenceThreadLocal<Rect> {
- @Override
- protected Rect initialValue() {
- return new Rect();
- }
- }
-
- private static class BitmapFactoryOptionsCache extends
- SoftReferenceThreadLocal<BitmapFactory.Options> {
- @Override
- protected BitmapFactory.Options initialValue() {
- return new BitmapFactory.Options();
- }
- }
-
private static final String TAG = "WidgetPreviewLoader";
- private static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version";
+ private static final boolean DEBUG = false;
private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f;
- private static final HashSet<String> sInvalidPackages = new HashSet<String>();
-
- // Used for drawing shortcut previews
- private final BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
- private final PaintCache mCachedShortcutPreviewPaint = new PaintCache();
- private final CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
-
- // Used for drawing widget previews
- private final CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
- private final RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
- private final RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
- private final PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
- private final PaintCache mDefaultAppWidgetPreviewPaint = new PaintCache();
- private final BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache();
- private final HashMap<String, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>();
- private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>();
+ private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
+ private final HashMap<WidgetCacheKey, WeakReference<Bitmap>> mLoadedPreviews = new HashMap<>();
+ private Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
private final Context mContext;
- private final int mAppIconSize;
private final IconCache mIconCache;
+ private final UserManagerCompat mUserManager;
private final AppWidgetManagerCompat mManager;
-
- private int mPreviewBitmapWidth;
- private int mPreviewBitmapHeight;
- private String mSize;
- private PagedViewCellLayout mWidgetSpacingLayout;
-
- private String mCachedSelectQuery;
-
-
- private CacheDb mDb;
+ private final CacheDb mDb;
private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
- public WidgetPreviewLoader(Context context) {
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
+ public WidgetPreviewLoader(Context context, IconCache iconCache) {
mContext = context;
- mAppIconSize = grid.iconSizePx;
- mIconCache = app.getIconCache();
+ mIconCache = iconCache;
mManager = AppWidgetManagerCompat.getInstance(context);
-
- mDb = app.getWidgetPreviewCacheDb();
-
- SharedPreferences sp = context.getSharedPreferences(
- LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
- final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null);
- final String versionName = android.os.Build.VERSION.INCREMENTAL;
- final boolean isLollipopOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
- if (!versionName.equals(lastVersionName)) {
- try {
- // clear all the previews whenever the system version changes, to ensure that
- // previews are up-to-date for any apps that might have been updated with the system
- clearDb();
- } catch (SQLiteReadOnlyDatabaseException e) {
- if (isLollipopOrGreater) {
- // Workaround for Bug. 18554839, if we fail to clear the db due to the read-only
- // issue, then ignore this error and leave the old previews
- } else {
- throw e;
- }
- } finally {
- SharedPreferences.Editor editor = sp.edit();
- editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName);
- editor.commit();
- }
- }
- }
-
- public void recreateDb() {
- LauncherAppState app = LauncherAppState.getInstance();
- app.recreateWidgetPreviewDb();
- mDb = app.getWidgetPreviewCacheDb();
- }
-
- public void setPreviewSize(int previewWidth, int previewHeight,
- PagedViewCellLayout widgetSpacingLayout) {
- mPreviewBitmapWidth = previewWidth;
- mPreviewBitmapHeight = previewHeight;
- mSize = previewWidth + "x" + previewHeight;
- mWidgetSpacingLayout = widgetSpacingLayout;
+ mUserManager = UserManagerCompat.getInstance(context);
+ mDb = new CacheDb(context);
}
- public Bitmap getPreview(final Object o) {
- final String name = getObjectName(o);
- final String packageName = getObjectPackage(o);
- // check if the package is valid
- synchronized(sInvalidPackages) {
- boolean packageValid = !sInvalidPackages.contains(packageName);
- if (!packageValid) {
- return null;
- }
- }
- synchronized(mLoadedPreviews) {
- // check if it exists in our existing cache
- if (mLoadedPreviews.containsKey(name)) {
- WeakReference<Bitmap> bitmapReference = mLoadedPreviews.get(name);
- Bitmap bitmap = bitmapReference.get();
- if (bitmap != null) {
- return bitmap;
- }
- }
- }
-
- Bitmap unusedBitmap = null;
- synchronized(mUnusedBitmaps) {
- // not in cache; we need to load it from the db
- while (unusedBitmap == null && mUnusedBitmaps.size() > 0) {
- Bitmap candidate = mUnusedBitmaps.remove(0).get();
- if (candidate != null && candidate.isMutable() &&
- candidate.getWidth() == mPreviewBitmapWidth &&
- candidate.getHeight() == mPreviewBitmapHeight) {
- unusedBitmap = candidate;
- }
- }
- if (unusedBitmap != null) {
- final Canvas c = mCachedAppWidgetPreviewCanvas.get();
- c.setBitmap(unusedBitmap);
- c.drawColor(0, PorterDuff.Mode.CLEAR);
- c.setBitmap(null);
- }
- }
-
- if (unusedBitmap == null) {
- unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight,
- Bitmap.Config.ARGB_8888);
- }
- Bitmap preview = readFromDb(name, unusedBitmap);
-
- if (preview != null) {
- synchronized(mLoadedPreviews) {
- mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
- }
- return preview;
- } else {
- // it's not in the db... we need to generate it
- final Bitmap generatedPreview = generatePreview(o, unusedBitmap);
- preview = generatedPreview;
- if (preview != unusedBitmap) {
- throw new RuntimeException("generatePreview is not recycling the bitmap " + o);
- }
-
- synchronized(mLoadedPreviews) {
- mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
- }
-
- // write to db on a thread pool... this can be done lazily and improves the performance
- // of the first time widget previews are loaded
- new AsyncTask<Void, Void, Void>() {
- public Void doInBackground(Void ... args) {
- writeToDb(o, generatedPreview);
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
-
- return preview;
- }
- }
+ /**
+ * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be
+ * called on UI thread
+ *
+ * @param o either {@link LauncherAppWidgetProviderInfo} or {@link ResolveInfo}
+ * @param immediateResult A bitmap array of size 1. If the result is already cached, it is
+ * set to the final result.
+ * @return a request id which can be used to cancel the request.
+ */
+ public PreviewLoadRequest getPreview(final Object o, int previewWidth, int previewHeight,
+ WidgetCell caller, Bitmap[] immediateResult) {
+ String size = previewWidth + "x" + previewHeight;
+ WidgetCacheKey key = getObjectKey(o, size);
- public void recycleBitmap(Object o, Bitmap bitmapToRecycle) {
- String name = getObjectName(o);
+ // Check if we have the preview loaded or not.
synchronized (mLoadedPreviews) {
- if (mLoadedPreviews.containsKey(name)) {
- Bitmap b = mLoadedPreviews.get(name).get();
- if (b == bitmapToRecycle) {
- mLoadedPreviews.remove(name);
- if (bitmapToRecycle.isMutable()) {
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(new SoftReference<Bitmap>(b));
- }
- }
- } else {
- throw new RuntimeException("Bitmap passed in doesn't match up");
- }
+ WeakReference<Bitmap> ref = mLoadedPreviews.get(key);
+ if (ref != null && ref.get() != null) {
+ immediateResult[0] = ref.get();
+ return new PreviewLoadRequest(null, key);
}
}
+
+ PreviewLoadTask task = new PreviewLoadTask(key, o, previewWidth, previewHeight, caller);
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ return new PreviewLoadRequest(task, key);
}
- static class CacheDb extends SQLiteOpenHelper {
- final static int DB_VERSION = 2;
- final static String TABLE_NAME = "shortcut_and_widget_previews";
- final static String COLUMN_NAME = "name";
- final static String COLUMN_SIZE = "size";
- final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
- Context mContext;
+ /**
+ * The DB holds the generated previews for various components. Previews can also have different
+ * sizes (landscape vs portrait).
+ */
+ private static class CacheDb extends SQLiteOpenHelper {
+ private static final int DB_VERSION = 3;
+
+ private static final String TABLE_NAME = "shortcut_and_widget_previews";
+ private static final String COLUMN_COMPONENT = "componentName";
+ private static final String COLUMN_USER = "profileId";
+ private static final String COLUMN_SIZE = "size";
+ private static final String COLUMN_PACKAGE = "packageName";
+ private static final String COLUMN_LAST_UPDATED = "lastUpdated";
+ private static final String COLUMN_VERSION = "version";
+ private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
public CacheDb(Context context) {
- super(context, new File(context.getCacheDir(),
- LauncherFiles.WIDGET_PREVIEWS_DB).getPath(), null, DB_VERSION);
- // Store the context for later use
- mContext = context;
+ super(context, LauncherFiles.WIDGET_PREVIEWS_DB, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
- COLUMN_NAME + " TEXT NOT NULL, " +
+ COLUMN_COMPONENT + " TEXT NOT NULL, " +
+ COLUMN_USER + " INTEGER NOT NULL, " +
COLUMN_SIZE + " TEXT NOT NULL, " +
- COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " +
- "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " +
+ COLUMN_PACKAGE + " TEXT NOT NULL, " +
+ COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
+ COLUMN_PREVIEW_BITMAP + " BLOB, " +
+ "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " +
");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
- // Delete all the records; they'll be repopulated as this is a cache
- db.execSQL("DELETE FROM " + TABLE_NAME);
+ clearDB(db);
}
}
- }
- private static final String WIDGET_PREFIX = "Widget:";
- private static final String SHORTCUT_PREFIX = "Shortcut:";
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
+ }
+ }
- private static String getObjectName(Object o) {
- // should cache the string builder
- StringBuilder sb = new StringBuilder();
- String output;
- if (o instanceof AppWidgetProviderInfo) {
- sb.append(WIDGET_PREFIX);
- sb.append(((AppWidgetProviderInfo) o).toString());
- output = sb.toString();
- sb.setLength(0);
- } else {
- sb.append(SHORTCUT_PREFIX);
- ResolveInfo info = (ResolveInfo) o;
- sb.append(new ComponentName(info.activityInfo.packageName,
- info.activityInfo.name).flattenToString());
- output = sb.toString();
- sb.setLength(0);
+ private void clearDB(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
}
- return output;
}
- private String getObjectPackage(Object o) {
- if (o instanceof AppWidgetProviderInfo) {
- return ((AppWidgetProviderInfo) o).provider.getPackageName();
+ private WidgetCacheKey getObjectKey(Object o, String size) {
+ // should cache the string builder
+ if (o instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) o;
+ return new WidgetCacheKey(info.provider, mManager.getUser(info), size);
} else {
ResolveInfo info = (ResolveInfo) o;
- return info.activityInfo.packageName;
+ return new WidgetCacheKey(
+ new ComponentName(info.activityInfo.packageName, info.activityInfo.name),
+ UserHandleCompat.myUserHandle(), size);
}
}
- private void writeToDb(Object o, Bitmap preview) {
- String name = getObjectName(o);
- SQLiteDatabase db = mDb.getWritableDatabase();
+ @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
ContentValues values = new ContentValues();
+ values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
+ values.put(CacheDb.COLUMN_USER, mUserManager.getSerialNumberForUser(key.user));
+ values.put(CacheDb.COLUMN_SIZE, key.size);
+ values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
+ values.put(CacheDb.COLUMN_VERSION, versions[0]);
+ values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
+ values.put(CacheDb.COLUMN_PREVIEW_BITMAP, Utilities.flattenBitmap(preview));
- values.put(CacheDb.COLUMN_NAME, name);
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
- values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
- values.put(CacheDb.COLUMN_SIZE, mSize);
try {
- db.insert(CacheDb.TABLE_NAME, null, values);
- } catch (SQLiteDiskIOException e) {
- recreateDb();
- } catch (SQLiteCantOpenDatabaseException e) {
- dumpOpenFiles();
- throw e;
+ mDb.getWritableDatabase().insertWithOnConflict(CacheDb.TABLE_NAME, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ } catch (SQLException e) {
+ Log.e(TAG, "Error saving image to DB", e);
}
}
- private void clearDb() {
- SQLiteDatabase db = mDb.getWritableDatabase();
- // Delete everything
+ public void removePackage(String packageName, UserHandleCompat user) {
+ removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
+ }
+
+ private void removePackage(String packageName, UserHandleCompat user, long userSerial) {
+ synchronized(mPackageVersions) {
+ mPackageVersions.remove(packageName);
+ }
+
+ synchronized (mLoadedPreviews) {
+ Set<WidgetCacheKey> keysToRemove = new HashSet<>();
+ for (WidgetCacheKey key : mLoadedPreviews.keySet()) {
+ if (key.componentName.getPackageName().equals(packageName) && key.user.equals(user)) {
+ keysToRemove.add(key);
+ }
+ }
+
+ for (WidgetCacheKey key : keysToRemove) {
+ WeakReference<Bitmap> req = mLoadedPreviews.remove(key);
+ if (req != null && req.get() != null) {
+ mUnusedBitmaps.add(req.get());
+ }
+ }
+ }
+
try {
- db.delete(CacheDb.TABLE_NAME, null, null);
- } catch (SQLiteDiskIOException e) {
- } catch (SQLiteCantOpenDatabaseException e) {
- dumpOpenFiles();
- throw e;
+ mDb.getWritableDatabase().delete(CacheDb.TABLE_NAME,
+ CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
+ new String[] {packageName, Long.toString(userSerial)});
+ } catch (SQLException e) {
+ Log.e(TAG, "Unable to delete items from DB", e);
}
}
- public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
- synchronized(sInvalidPackages) {
- sInvalidPackages.add(packageName);
+ /**
+ * Updates the persistent DB:
+ * 1. Any preview generated for an old package version is removed
+ * 2. Any preview for an absent package is removed
+ * This ensures that we remove entries for packages which changed while the launcher was dead.
+ */
+ public void removeObsoletePreviews() {
+ LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
+ LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
+
+ for (Object obj : LauncherModel.getSortedWidgetsAndShortcuts(mContext, false)) {
+ final UserHandleCompat user;
+ final String pkg;
+ if (obj instanceof ResolveInfo) {
+ user = UserHandleCompat.myUserHandle();
+ pkg = ((ResolveInfo) obj).activityInfo.packageName;
+ } else {
+ LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) obj;
+ user = mManager.getUser(info);
+ pkg = info.provider.getPackageName();
+ }
+
+ int userIdIndex = userIdCache.indexOfValue(user);
+ final long userId;
+ if (userIdIndex < 0) {
+ userId = mUserManager.getSerialNumberForUser(user);
+ userIdCache.put(userId, user);
+ } else {
+ userId = userIdCache.keyAt(userIdIndex);
+ }
+
+ HashSet<String> packages = validPackages.get(userId);
+ if (packages == null) {
+ packages = new HashSet<>();
+ validPackages.put(userId, packages);
+ }
+ packages.add(pkg);
}
- new AsyncTask<Void, Void, Void>() {
- public Void doInBackground(Void ... args) {
- SQLiteDatabase db = cacheDb.getWritableDatabase();
- try {
- db.delete(CacheDb.TABLE_NAME,
- CacheDb.COLUMN_NAME + " LIKE ? OR " +
- CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
- new String[] {
- WIDGET_PREFIX + packageName + "/%",
- SHORTCUT_PREFIX + packageName + "/%"
- } // args to SELECT query
- );
- } catch (SQLiteDiskIOException e) {
- } catch (SQLiteCantOpenDatabaseException e) {
- dumpOpenFiles();
- throw e;
+
+ LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
+ Cursor c = null;
+ try {
+ c = mDb.getReadableDatabase().query(CacheDb.TABLE_NAME,
+ new String[] {CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
+ CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
+ null, null, null, null, null);
+ while (c.moveToNext()) {
+ long userId = c.getLong(0);
+ String pkg = c.getString(1);
+ long lastUpdated = c.getLong(2);
+ long version = c.getLong(3);
+
+ HashSet<String> packages = validPackages.get(userId);
+ if (packages != null && packages.contains(pkg)) {
+ long[] versions = getPackageVersion(pkg);
+ if (versions[0] == version && versions[1] == lastUpdated) {
+ // Every thing checks out
+ continue;
+ }
}
- synchronized(sInvalidPackages) {
- sInvalidPackages.remove(packageName);
+
+ // We need to delete this package.
+ packages = packagesToDelete.get(userId);
+ if (packages == null) {
+ packages = new HashSet<>();
+ packagesToDelete.put(userId, packages);
}
- return null;
+ packages.add(pkg);
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
- }
- private static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
- new AsyncTask<Void, Void, Void>() {
- public Void doInBackground(Void ... args) {
- SQLiteDatabase db = cacheDb.getWritableDatabase();
- try {
- db.delete(CacheDb.TABLE_NAME,
- CacheDb.COLUMN_NAME + " = ? ", // SELECT query
- new String[] { objectName }); // args to SELECT query
- } catch (SQLiteDiskIOException e) {
- } catch (SQLiteCantOpenDatabaseException e) {
- dumpOpenFiles();
- throw e;
+ for (int i = 0; i < packagesToDelete.size(); i++) {
+ long userId = packagesToDelete.keyAt(i);
+ UserHandleCompat user = mUserManager.getUserForSerialNumber(userId);
+ for (String pkg : packagesToDelete.valueAt(i)) {
+ removePackage(pkg, user, userId);
}
- return null;
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ } catch (SQLException e) {
+ Log.e(TAG, "Error updatating widget previews", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
}
- private Bitmap readFromDb(String name, Bitmap b) {
- if (mCachedSelectQuery == null) {
- mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
- CacheDb.COLUMN_SIZE + " = ?";
- }
- SQLiteDatabase db = mDb.getReadableDatabase();
- Cursor result;
+ private Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle) {
+ Cursor cursor = null;
try {
- result = db.query(CacheDb.TABLE_NAME,
- new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
- mCachedSelectQuery, // select query
- new String[] { name, mSize }, // args to select query
- null,
- null,
- null,
- null);
- } catch (SQLiteDiskIOException e) {
- recreateDb();
- return null;
- } catch (SQLiteCantOpenDatabaseException e) {
- dumpOpenFiles();
- throw e;
- }
- if (result.getCount() > 0) {
- result.moveToFirst();
- byte[] blob = result.getBlob(0);
- result.close();
- final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get();
- opts.inBitmap = b;
- opts.inSampleSize = 1;
- try {
- return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
- } catch (IllegalArgumentException e) {
- removeItemFromDb(mDb, name);
- return null;
+ cursor = mDb.getReadableDatabase().query(
+ CacheDb.TABLE_NAME,
+ new String[] { CacheDb.COLUMN_PREVIEW_BITMAP },
+ CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND " + CacheDb.COLUMN_SIZE + " = ?",
+ new String[] {
+ key.componentName.flattenToString(),
+ Long.toString(mUserManager.getSerialNumberForUser(key.user)),
+ key.size
+ },
+ null, null, null);
+ if (cursor.moveToNext()) {
+ byte[] blob = cursor.getBlob(0);
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inBitmap = recycle;
+ try {
+ return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ } catch (SQLException e) {
+ Log.w(TAG, "Error loading preview from DB", e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
}
- } else {
- result.close();
- return null;
}
+ return null;
}
- private Bitmap generatePreview(Object info, Bitmap preview) {
- if (preview != null &&
- (preview.getWidth() != mPreviewBitmapWidth ||
- preview.getHeight() != mPreviewBitmapHeight)) {
- throw new RuntimeException("Improperly sized bitmap passed as argument");
- }
+ private Bitmap generatePreview(Object info, Bitmap recycle, int previewWidth, int previewHeight) {
if (info instanceof LauncherAppWidgetProviderInfo) {
- return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, preview);
+ return generateWidgetPreview((LauncherAppWidgetProviderInfo) info, previewWidth, recycle);
} else {
return generateShortcutPreview(
- (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
+ (ResolveInfo) info, previewWidth, previewHeight, recycle);
}
}
- public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, Bitmap preview) {
- int maxWidth = maxWidthForWidgetPreview(info.spanX);
- int maxHeight = maxHeightForWidgetPreview(info.spanY);
- return generateWidgetPreview(info, info.spanX, info.spanY, maxWidth,
- maxHeight, preview, null);
- }
-
- public int maxWidthForWidgetPreview(int spanX) {
- return Math.min(mPreviewBitmapWidth,
- mWidgetSpacingLayout.estimateCellWidth(spanX));
- }
-
- public int maxHeightForWidgetPreview(int spanY) {
- return Math.min(mPreviewBitmapHeight,
- mWidgetSpacingLayout.estimateCellHeight(spanY));
+ public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
+ int previewWidth, Bitmap preview) {
+ int maxWidth = Math.min(previewWidth, info.spanX
+ * LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile().cellWidthPx);
+ return generateWidgetPreview(info, maxWidth, preview, null);
}
- public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, int cellHSpan, int cellVSpan,
- int maxPreviewWidth, int maxPreviewHeight, Bitmap preview, int[] preScaledWidthOut) {
+ public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
+ int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
- if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE;
Drawable drawable = null;
if (info.previewImage != 0) {
@@ -531,61 +368,23 @@ public class WidgetPreviewLoader {
}
}
+ final boolean widgetPreviewExists = (drawable != null);
+ final int spanX = info.spanX < 1 ? 1 : info.spanX;
+ final int spanY = info.spanY < 1 ? 1 : info.spanY;
+
int previewWidth;
int previewHeight;
- Bitmap defaultPreview = null;
- boolean widgetPreviewExists = (drawable != null);
+ Bitmap tileBitmap = null;
+
if (widgetPreviewExists) {
previewWidth = drawable.getIntrinsicWidth();
previewHeight = drawable.getIntrinsicHeight();
} else {
// Generate a preview image if we couldn't load one
- if (cellHSpan < 1) cellHSpan = 1;
- if (cellVSpan < 1) cellVSpan = 1;
-
- // This Drawable is not directly drawn, so there's no need to mutate it.
- BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
- .getDrawable(R.drawable.widget_tile);
- final int previewDrawableWidth = previewDrawable
- .getIntrinsicWidth();
- final int previewDrawableHeight = previewDrawable
- .getIntrinsicHeight();
- previewWidth = previewDrawableWidth * cellHSpan;
- previewHeight = previewDrawableHeight * cellVSpan;
-
- defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
- final Canvas c = mCachedAppWidgetPreviewCanvas.get();
- c.setBitmap(defaultPreview);
- Paint p = mDefaultAppWidgetPreviewPaint.get();
- if (p == null) {
- p = new Paint();
- p.setShader(new BitmapShader(previewDrawable.getBitmap(),
- Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
- mDefaultAppWidgetPreviewPaint.set(p);
- }
- final Rect dest = mCachedAppWidgetPreviewDestRect.get();
- dest.set(0, 0, previewWidth, previewHeight);
- c.drawRect(dest, p);
- c.setBitmap(null);
-
- // Draw the icon in the top left corner
- int minOffset = (int) (mAppIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
- int smallestSide = Math.min(previewWidth, previewHeight);
- float iconScale = Math.min((float) smallestSide
- / (mAppIconSize + 2 * minOffset), 1f);
-
- try {
- Drawable icon = mManager.loadIcon(info, mIconCache);
- if (icon != null) {
- int hoffset = (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
- int yoffset = (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
- icon = mutateOnMainThread(icon);
- renderDrawableToBitmap(icon, defaultPreview, hoffset,
- yoffset, (int) (mAppIconSize * iconScale),
- (int) (mAppIconSize * iconScale));
- }
- } catch (Resources.NotFoundException e) {
- }
+ tileBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(
+ R.drawable.widget_tile)).getBitmap();
+ previewWidth = tileBitmap.getWidth() * spanX;
+ previewHeight = tileBitmap.getHeight() * spanY;
}
// Scale to fit width only - let the widget preview be clipped in the
@@ -603,30 +402,60 @@ public class WidgetPreviewLoader {
}
// If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size
+ final Canvas c = new Canvas();
if (preview == null) {
preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
+ c.setBitmap(preview);
+ } else {
+ // Reusing bitmap. Clear it.
+ c.setBitmap(preview);
+ c.drawColor(0, PorterDuff.Mode.CLEAR);
}
// Draw the scaled preview into the final bitmap
int x = (preview.getWidth() - previewWidth) / 2;
if (widgetPreviewExists) {
- renderDrawableToBitmap(drawable, preview, x, 0, previewWidth,
- previewHeight);
+ drawable.setBounds(x, 0, x + previewWidth, previewHeight);
+ drawable.draw(c);
} 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(x, 0, x + previewWidth, previewHeight);
-
- Paint p = mCachedAppWidgetPreviewPaint.get();
- if (p == null) {
- p = new Paint();
- p.setFilterBitmap(true);
- mCachedAppWidgetPreviewPaint.set(p);
+ final Paint p = new Paint();
+ p.setFilterBitmap(true);
+ int appIconSize = LauncherAppState.getInstance().getDynamicGrid()
+ .getDeviceProfile().iconSizePx;
+
+ // draw the spanX x spanY tiles
+ final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight());
+
+ float tileW = scale * tileBitmap.getWidth();
+ float tileH = scale * tileBitmap.getHeight();
+ final RectF dst = new RectF(0, 0, tileW, tileH);
+
+ float tx = x;
+ for (int i = 0; i < spanX; i++, tx += tileW) {
+ float ty = 0;
+ for (int j = 0; j < spanY; j++, ty += tileH) {
+ dst.offsetTo(tx, ty);
+ c.drawBitmap(tileBitmap, src, dst, p);
+ }
}
- c.drawBitmap(defaultPreview, src, dest, p);
+
+ // Draw the icon in the top left corner
+ // TODO: use top right for RTL
+ int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
+ int smallestSide = Math.min(previewWidth, previewHeight);
+ float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale);
+
+ try {
+ Drawable icon = mutateOnMainThread(mManager.loadIcon(info, mIconCache));
+ if (icon != null) {
+ int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x;
+ int yoffset = (int) ((tileH - appIconSize * iconScale) / 2);
+ icon.setBounds(hoffset, yoffset,
+ hoffset + (int) (appIconSize * iconScale),
+ yoffset + (int) (appIconSize * iconScale));
+ icon.draw(c);
+ }
+ } catch (Resources.NotFoundException e) { }
c.setBitmap(null);
}
return mManager.getBadgeBitmap(info, preview);
@@ -634,71 +463,49 @@ public class WidgetPreviewLoader {
private Bitmap generateShortcutPreview(
ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) {
- 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);
+ final Canvas c = new Canvas();
+ if (preview == null) {
+ preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+ c.setBitmap(preview);
+ } else if (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight) {
+ throw new RuntimeException("Improperly sized bitmap passed as argument");
} else {
- c.setBitmap(tempBitmap);
+ // Reusing bitmap. Clear it.
+ c.setBitmap(preview);
c.drawColor(0, PorterDuff.Mode.CLEAR);
- c.setBitmap(null);
}
- // Render the icon
- Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info.activityInfo));
- int paddingTop = mContext.
- getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
- int paddingLeft = mContext.
- getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
- int paddingRight = mContext.
- getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
+ Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info.activityInfo));
+ icon.setFilterBitmap(true);
+ // Draw a desaturated/scaled version of the icon in the background as a watermark
+ ColorMatrix colorMatrix = new ColorMatrix();
+ colorMatrix.setSaturation(0);
+ icon.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+ icon.setAlpha((int) (255 * 0.06f));
+
+ Resources res = mContext.getResources();
+ int paddingTop = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
+ int paddingLeft = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
+ int paddingRight = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
+ icon.setBounds(paddingLeft, paddingTop,
+ paddingLeft + scaledIconWidth, paddingTop + scaledIconWidth);
+ icon.draw(c);
+
+ // Draw the final icon at top left corner.
+ // TODO: use top right for RTL
+ int appIconSize = LauncherAppState.getInstance().getDynamicGrid()
+ .getDeviceProfile().iconSizePx;
+ icon.setAlpha(255);
+ icon.setColorFilter(null);
+ icon.setBounds(0, 0, appIconSize, appIconSize);
+ icon.draw(c);
- renderDrawableToBitmap(
- icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
-
- if (preview != null &&
- (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) {
- throw new RuntimeException("Improperly sized bitmap passed as argument");
- } else if (preview == null) {
- preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
- }
-
- c.setBitmap(preview);
- // Draw a desaturated/scaled version of the icon in the background as a watermark
- 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));
- mCachedShortcutPreviewPaint.set(p);
- }
- c.drawBitmap(tempBitmap, 0, 0, p);
c.setBitmap(null);
-
- renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
-
return preview;
}
- private static void renderDrawableToBitmap(
- Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
- if (bitmap != null) {
- Canvas c = new Canvas(bitmap);
- 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 Drawable mutateOnMainThread(final Drawable drawable) {
try {
return mMainThreadExecutor.submit(new Callable<Drawable>() {
@@ -715,82 +522,148 @@ public class WidgetPreviewLoader {
}
}
- private static final int MAX_OPEN_FILES = 1024;
- private static final int SAMPLE_RATE = 23;
/**
- * Dumps all files that are open in this process without allocating a file descriptor.
+ * @return an array of containing versionCode and lastUpdatedTime for the package.
*/
- private static void dumpOpenFiles() {
- try {
- Log.i(TAG, "DUMP OF OPEN FILES (sample rate: 1 every " + SAMPLE_RATE + "):");
- final String TYPE_APK = "apk";
- final String TYPE_JAR = "jar";
- final String TYPE_PIPE = "pipe";
- final String TYPE_SOCKET = "socket";
- final String TYPE_DB = "db";
- final String TYPE_ANON_INODE = "anon_inode";
- final String TYPE_DEV = "dev";
- final String TYPE_NON_FS = "non-fs";
- final String TYPE_OTHER = "other";
- List<String> types = Arrays.asList(TYPE_APK, TYPE_JAR, TYPE_PIPE, TYPE_SOCKET, TYPE_DB,
- TYPE_ANON_INODE, TYPE_DEV, TYPE_NON_FS, TYPE_OTHER);
- int[] count = new int[types.size()];
- int[] duplicates = new int[types.size()];
- HashSet<String> files = new HashSet<String>();
- int total = 0;
- for (int i = 0; i < MAX_OPEN_FILES; i++) {
- // This is a gigantic hack but unfortunately the only way to resolve an fd
- // to a file name. Note that we have to loop over all possible fds because
- // reading the directory would require allocating a new fd. The kernel is
- // currently implemented such that no fd is larger then the current rlimit,
- // which is why it's safe to loop over them in such a way.
- String fd = "/proc/self/fd/" + i;
+ private long[] getPackageVersion(String packageName) {
+ synchronized (mPackageVersions) {
+ long[] versions = mPackageVersions.get(packageName);
+ if (versions == null) {
+ versions = new long[2];
try {
- // getCanonicalPath() uses readlink behind the scene which doesn't require
- // a file descriptor.
- String resolved = new File(fd).getCanonicalPath();
- int type = types.indexOf(TYPE_OTHER);
- if (resolved.startsWith("/dev/")) {
- type = types.indexOf(TYPE_DEV);
- } else if (resolved.endsWith(".apk")) {
- type = types.indexOf(TYPE_APK);
- } else if (resolved.endsWith(".jar")) {
- type = types.indexOf(TYPE_JAR);
- } else if (resolved.contains("/fd/pipe:")) {
- type = types.indexOf(TYPE_PIPE);
- } else if (resolved.contains("/fd/socket:")) {
- type = types.indexOf(TYPE_SOCKET);
- } else if (resolved.contains("/fd/anon_inode:")) {
- type = types.indexOf(TYPE_ANON_INODE);
- } else if (resolved.endsWith(".db") || resolved.contains("/databases/")) {
- type = types.indexOf(TYPE_DB);
- } else if (resolved.startsWith("/proc/") && resolved.contains("/fd/")) {
- // Those are the files that don't point anywhere on the file system.
- // getCanonicalPath() wrongly interprets these as relative symlinks and
- // resolves them within /proc/<pid>/fd/.
- type = types.indexOf(TYPE_NON_FS);
- }
- count[type]++;
- total++;
- if (files.contains(resolved)) {
- duplicates[type]++;
+ PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
+ versions[0] = info.versionCode;
+ versions[1] = info.lastUpdateTime;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "PackageInfo not found", e);
+ }
+ mPackageVersions.put(packageName, versions);
+ }
+ return versions;
+ }
+ }
+
+ /**
+ * A request Id which can be used by the client to cancel any request.
+ */
+ public class PreviewLoadRequest {
+
+ private final PreviewLoadTask mTask;
+ private final WidgetCacheKey mKey;
+
+ public PreviewLoadRequest(PreviewLoadTask task, WidgetCacheKey key) {
+ mTask = task;
+ mKey = key;
+ }
+
+ public void cancel(boolean recycleImage) {
+ if (mTask != null) {
+ mTask.cancel(true);
+ }
+
+ if (recycleImage) {
+ synchronized(mLoadedPreviews) {
+ WeakReference<Bitmap> result = mLoadedPreviews.remove(mKey);
+ if (result != null && result.get() != null) {
+ mUnusedBitmaps.add(result.get());
}
- files.add(resolved);
- if (total % SAMPLE_RATE == 0) {
- Log.i(TAG, " fd " + i + ": " + resolved
- + " (" + types.get(type) + ")");
+ }
+ }
+ }
+ }
+
+ public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> {
+
+ private final WidgetCacheKey mKey;
+ private final Object mInfo;
+ private final int mPreviewHeight;
+ private final int mPreviewWidth;
+ private final WidgetCell mCaller;
+
+ PreviewLoadTask(WidgetCacheKey key, Object info, int previewWidth,
+ int previewHeight, WidgetCell caller) {
+ mKey = key;
+ mInfo = info;
+ mPreviewHeight = previewHeight;
+ mPreviewWidth = previewWidth;
+ mCaller = caller;
+ if (DEBUG) {
+ Log.d(TAG, String.format("%s, %s, %d, %d",
+ mKey, mInfo, mPreviewHeight, mPreviewWidth));
+ }
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ Bitmap unusedBitmap = null;
+
+ // TODO(hyunyoungs): Figure out why this path causes concurrency issue.
+ synchronized (mUnusedBitmaps) {
+ // Check if we can use a bitmap
+ for (Bitmap candidate : mUnusedBitmaps) {
+ if (candidate != null && candidate.isMutable() &&
+ candidate.getWidth() == mPreviewWidth &&
+ candidate.getHeight() == mPreviewHeight) {
+ unusedBitmap = candidate;
+ break;
}
- } catch (IOException e) {
- // Ignoring exceptions for non-existing file descriptors.
+ }
+
+ if (unusedBitmap == null) {
+ unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
+ } else {
+ mUnusedBitmaps.remove(unusedBitmap);
}
}
- for (int i = 0; i < types.size(); i++) {
- Log.i(TAG, String.format("Open %10s files: %4d total, %4d duplicates",
- types.get(i), count[i], duplicates[i]));
+ if (isCancelled()) {
+ return null;
}
- } catch (Throwable t) {
- // Catch everything. This is called from an exception handler that we shouldn't upset.
- Log.e(TAG, "Unable to log open files.", t);
+ Bitmap preview = readFromDb(mKey, unusedBitmap);
+ if (!isCancelled() && preview == null) {
+ // Fetch the version info before we generate the preview, so that, in-case the
+ // app was updated while we are generating the preview, we use the old version info,
+ // which would gets re-written next time.
+ long[] versions = getPackageVersion(mKey.componentName.getPackageName());
+
+ // it's not in the db... we need to generate it
+ preview = generatePreview(mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
+
+ if (!isCancelled()) {
+ writeToDb(mKey, versions, preview);
+ }
+ }
+
+ return preview;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ synchronized(mLoadedPreviews) {
+ mLoadedPreviews.put(mKey, new WeakReference<Bitmap>(result));
+ }
+
+ mCaller.applyPreview(result);
+ }
+ }
+
+ private static final class WidgetCacheKey extends ComponentKey {
+
+ // TODO: remove dependency on size
+ private final String size;
+
+ public WidgetCacheKey(ComponentName componentName, UserHandleCompat user, String size) {
+ super(componentName, user);
+ this.size = size;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() ^ size.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && ((WidgetCacheKey) o).size.equals(size);
}
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 95215402c..6b03e3100 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -26,6 +26,7 @@ import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
import android.app.WallpaperManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
@@ -43,12 +44,11 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
-import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -66,9 +66,14 @@ import com.android.launcher3.FolderIcon.FolderRingAnimator;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.Launcher.LauncherOverlay;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.UninstallDropTarget.UninstallSource;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.WallpaperUtils;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
import java.util.HashMap;
@@ -84,7 +89,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class Workspace extends SmoothPagedView
implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
- Insettable {
+ Insettable, UninstallSource {
private static final String TAG = "Launcher.Workspace";
private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
@@ -117,24 +122,24 @@ public class Workspace extends SmoothPagedView
private long mCustomContentShowTime = -1;
private LayoutTransition mLayoutTransition;
- private final WallpaperManager mWallpaperManager;
- private IBinder mWindowToken;
+ @Thunk final WallpaperManager mWallpaperManager;
+ @Thunk IBinder mWindowToken;
private int mOriginalDefaultPage;
private int mDefaultPage;
private ShortcutAndWidgetContainer mDragSourceInternal;
- private static boolean sAccessibilityEnabled;
+ @Thunk static boolean sAccessibilityEnabled;
// The screen id used for the empty screen always present to the right.
final static long EXTRA_EMPTY_SCREEN_ID = -201;
private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
- private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
- private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
+ @Thunk HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
+ @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>();
- private Runnable mRemoveEmptyScreenRunnable;
- private boolean mDeferRemoveExtraEmptyScreen = false;
+ @Thunk Runnable mRemoveEmptyScreenRunnable;
+ @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
/**
* CellInfo for the cell that is currently being dragged
@@ -144,7 +149,7 @@ public class Workspace extends SmoothPagedView
/**
* Target drop area calculated during last acceptDrop call.
*/
- private int[] mTargetCell = new int[2];
+ @Thunk int[] mTargetCell = new int[2];
private int mDragOverX = -1;
private int mDragOverY = -1;
@@ -159,7 +164,7 @@ public class Workspace extends SmoothPagedView
/**
* The CellLayout that is currently being dragged over
*/
- private CellLayout mDragTargetLayout = null;
+ @Thunk CellLayout mDragTargetLayout = null;
/**
* The CellLayout that we will show as glowing
*/
@@ -170,16 +175,16 @@ public class Workspace extends SmoothPagedView
*/
private CellLayout mDropToLayout = null;
- private Launcher mLauncher;
- private IconCache mIconCache;
- private DragController mDragController;
+ @Thunk Launcher mLauncher;
+ @Thunk IconCache mIconCache;
+ @Thunk 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[] mTempPt = new int[2];
private int[] mTempEstimate = new int[2];
- private float[] mDragViewVisualCenter = new float[2];
+ @Thunk float[] mDragViewVisualCenter = new float[2];
private float[] mTempCellLayoutCenterCoordinates = new float[2];
private Matrix mTempInverseMatrix = new Matrix();
@@ -204,7 +209,7 @@ public class Workspace extends SmoothPagedView
private boolean mInScrollArea = false;
private HolographicOutlineHelper mOutlineHelper;
- private Bitmap mDragOutline = null;
+ @Thunk Bitmap mDragOutline = null;
private static final Rect sTempRect = new Rect();
private final int[] mTempXY = new int[2];
private int[] mTempVisiblePagesRange = new int[2];
@@ -213,11 +218,11 @@ public class Workspace extends SmoothPagedView
private boolean mWorkspaceFadeInAdjacentScreens;
WallpaperOffsetInterpolator mWallpaperOffset;
- private boolean mWallpaperIsLiveWallpaper;
- private int mNumPagesForWallpaperParallax;
- private float mLastSetWallpaperOffsetSteps = 0;
+ @Thunk boolean mWallpaperIsLiveWallpaper;
+ @Thunk int mNumPagesForWallpaperParallax;
+ @Thunk float mLastSetWallpaperOffsetSteps = 0;
- private Runnable mDelayedResizeRunnable;
+ @Thunk Runnable mDelayedResizeRunnable;
private Runnable mDelayedSnapToPageRunnable;
private Point mDisplaySize = new Point();
@@ -226,7 +231,7 @@ public class Workspace extends SmoothPagedView
public static final int REORDER_TIMEOUT = 350;
private final Alarm mFolderCreationAlarm = new Alarm();
private final Alarm mReorderAlarm = new Alarm();
- private FolderRingAnimator mDragFolderRingAnimator = null;
+ @Thunk FolderRingAnimator mDragFolderRingAnimator = null;
private FolderIcon mDragOverFolderIcon = null;
private boolean mCreateUserFolderOnDrop = false;
private boolean mAddToExistingFolderOnDrop = false;
@@ -255,8 +260,8 @@ public class Workspace extends SmoothPagedView
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;
+ @Thunk int mLastReorderX = -1;
+ @Thunk int mLastReorderY = -1;
private SparseArray<Parcelable> mSavedStates;
private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
@@ -268,17 +273,17 @@ public class Workspace extends SmoothPagedView
private float mCurrentScale;
private float mNewScale;
- private float[] mOldBackgroundAlphas;
+ @Thunk float[] mOldBackgroundAlphas;
private float[] mOldAlphas;
- private float[] mNewBackgroundAlphas;
+ @Thunk float[] mNewBackgroundAlphas;
private float[] mNewAlphas;
private int mLastChildCount = -1;
private float mTransitionProgress;
- private Animator mStateAnimator = null;
+ @Thunk Animator mStateAnimator = null;
float mOverScrollEffect = 0f;
- private Runnable mDeferredAction;
+ @Thunk Runnable mDeferredAction;
private boolean mDeferDropAfterUninstall;
private boolean mUninstallSuccessful;
@@ -365,13 +370,12 @@ public class Workspace extends SmoothPagedView
// 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) {
+ public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
int[] size = new int[2];
if (getChildCount() > 0) {
// Use the first non-custom page to estimate the child position
CellLayout cl = (CellLayout) getChildAt(numCustomPages());
- Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
+ Rect r = estimateItemPosition(cl, itemInfo, 0, 0, itemInfo.spanX, itemInfo.spanY);
size[0] = r.width();
size[1] = r.height();
if (springLoaded) {
@@ -401,7 +405,6 @@ public class Workspace extends SmoothPagedView
setChildrenBackgroundAlphaMultipliers(1f);
// Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
InstallShortcutReceiver.enableInstallQueue();
- UninstallShortcutReceiver.enableUninstallQueue();
post(new Runnable() {
@Override
public void run() {
@@ -429,7 +432,6 @@ public class Workspace extends SmoothPagedView
// Re-enable any Un/InstallShortcutReceiver and now process any queued items
InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
- UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
mDragSourceInternal = null;
mLauncher.onInteractionEnd();
@@ -492,7 +494,7 @@ public class Workspace extends SmoothPagedView
CellLayout cl = ((CellLayout) child);
cl.setOnInterceptTouchListener(this);
cl.setClickable(true);
- cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
super.onChildViewAdded(parent, child);
}
@@ -561,10 +563,6 @@ public class Workspace extends SmoothPagedView
}
public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
- " at index: " + insertIndex, true);
-
if (mWorkspaceScreens.containsKey(screenId)) {
throw new RuntimeException("Screen id " + screenId + " already exists!");
}
@@ -578,6 +576,12 @@ public class Workspace extends SmoothPagedView
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
+
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ if (delegate != null && delegate.isInAccessibleDrag()) {
+ newScreen.enableAccessibleDrag(true);
+ }
return screenId;
}
@@ -664,9 +668,6 @@ public class Workspace extends SmoothPagedView
}
public void addExtraEmptyScreenOnDrag() {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
-
boolean lastChildOnScreen = false;
boolean childOnFinalScreen = false;
@@ -693,9 +694,6 @@ public class Workspace extends SmoothPagedView
}
public boolean addExtraEmptyScreen() {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
-
if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
return true;
@@ -704,9 +702,6 @@ public class Workspace extends SmoothPagedView
}
private void convertFinalScreenToEmptyScreenIfNecessary() {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
-
if (mLauncher.isWorkspaceLoading()) {
// Invalid and dangerous operation if workspace is loading
Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
@@ -731,7 +726,6 @@ public class Workspace extends SmoothPagedView
// Update the model if we have changed any screens
mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
- Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
}
}
@@ -741,8 +735,6 @@ public class Workspace extends SmoothPagedView
public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
final int delay, final boolean stripEmptyScreens) {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
if (mLauncher.isWorkspaceLoading()) {
// Don't strip empty screens if the workspace is still loading
Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
@@ -784,9 +776,7 @@ public class Workspace extends SmoothPagedView
private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
final boolean stripEmptyScreens) {
- // Log to disk
// XXX: Do we need to update LM workspace screens below?
- Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
@@ -830,8 +820,6 @@ public class Workspace extends SmoothPagedView
}
public long commitExtraEmptyScreen() {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
if (mLauncher.isWorkspaceLoading()) {
// Invalid and dangerous operation if workspace is loading
Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
@@ -890,9 +878,6 @@ public class Workspace extends SmoothPagedView
}
public void stripEmptyScreens() {
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
-
if (mLauncher.isWorkspaceLoading()) {
// Don't strip empty screens if the workspace is still loading.
// This is dangerous and can result in data loss.
@@ -920,7 +905,6 @@ public class Workspace extends SmoothPagedView
int pageShift = 0;
for (Long id: removeScreens) {
- Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
CellLayout cl = mWorkspaceScreens.get(id);
mWorkspaceScreens.remove(id);
mScreenOrder.remove(id);
@@ -1334,10 +1318,10 @@ public class Workspace extends SmoothPagedView
protected void setWallpaperDimension() {
new AsyncTask<Void, Void, Void>() {
public Void doInBackground(Void ... args) {
- String spKey = WallpaperCropActivity.getSharedPreferencesKey();
+ String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY;
SharedPreferences sp =
mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
- LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
+ WallpaperUtils.suggestWallpaperDimension(mLauncher.getResources(),
sp, mLauncher.getWindowManager(), mWallpaperManager,
mLauncher.overrideWallpaperDimensions());
return null;
@@ -1427,7 +1411,22 @@ public class Workspace extends SmoothPagedView
}
private float wallpaperOffsetForCurrentScroll() {
+ // TODO: do different behavior if it's a live wallpaper?
+ // Don't use up all the wallpaper parallax until you have at least
+ // MIN_PARALLAX_PAGE_SPAN pages
+ int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
+ int parallaxPageSpan;
+ if (mWallpaperIsLiveWallpaper) {
+ parallaxPageSpan = numScrollingPages - 1;
+ } else {
+ parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
+ }
+ mNumPagesForWallpaperParallax = parallaxPageSpan;
+
if (getChildCount() <= 1) {
+ if (isLayoutRtl()) {
+ return 1 - 1.0f/mNumPagesForWallpaperParallax;
+ }
return 0;
}
@@ -1447,28 +1446,20 @@ public class Workspace extends SmoothPagedView
if (scrollRange == 0) {
return 0;
} else {
- // TODO: do different behavior if it's a live wallpaper?
// Sometimes the left parameter of the pages is animated during a layout transition;
// this parameter offsets it to keep the wallpaper from animating as well
int adjustedScroll =
getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
float offset = Math.min(1, adjustedScroll / (float) scrollRange);
offset = Math.max(0, offset);
- // Don't use up all the wallpaper parallax until you have at least
- // MIN_PARALLAX_PAGE_SPAN pages
- int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
- int parallaxPageSpan;
- if (mWallpaperIsLiveWallpaper) {
- parallaxPageSpan = numScrollingPages - 1;
- } else {
- parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
- }
- mNumPagesForWallpaperParallax = parallaxPageSpan;
// On RTL devices, push the wallpaper offset to the right if we don't have enough
// pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
- int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
- return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
+ if (!mWallpaperIsLiveWallpaper && numScrollingPages < MIN_PARALLAX_PAGE_SPAN
+ && isLayoutRtl()) {
+ return offset * (parallaxPageSpan - numScrollingPages + 1) / parallaxPageSpan;
+ }
+ return offset * (numScrollingPages - 1) / parallaxPageSpan;
}
}
@@ -1548,7 +1539,7 @@ public class Workspace extends SmoothPagedView
@Override
public void announceForAccessibility(CharSequence text) {
// Don't announce if apps is on top of us.
- if (!mLauncher.isAllAppsVisible()) {
+ if (!mLauncher.isAppsViewVisible()) {
super.announceForAccessibility(text);
}
}
@@ -1647,7 +1638,6 @@ public class Workspace extends SmoothPagedView
float scrollProgress = getScrollProgress(screenCenter, child, i);
float alpha = 1 - Math.abs(scrollProgress);
child.getShortcutsAndWidgets().setAlpha(alpha);
- //child.setBackgroundAlphaMultiplier(1 - alpha);
}
}
}
@@ -1660,6 +1650,23 @@ public class Workspace extends SmoothPagedView
}
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public void enableAccessibleDrag(boolean enable) {
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout child = (CellLayout) getChildAt(i);
+ child.enableAccessibleDrag(enable);
+ }
+
+ if (enable) {
+ // We need to allow our individual children to become click handlers in this case
+ setOnClickListener(null);
+ } else {
+ // Reset our click listener
+ setOnClickListener(mLauncher);
+ }
+ mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable);
+ }
+
public boolean hasCustomContent() {
return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
}
@@ -1701,7 +1708,11 @@ public class Workspace extends SmoothPagedView
mLastCustomContentScrollProgress = progress;
- mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8f);
+ // We should only update the drag layer background alpha if we are not in all apps or the
+ // widgets tray
+ if (mState == State.NORMAL) {
+ mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8f);
+ }
if (mLauncher.getHotseat() != null) {
mLauncher.getHotseat().setTranslationX(translationX);
@@ -1820,7 +1831,7 @@ public class Workspace extends SmoothPagedView
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
- if (!mLauncher.isAllAppsVisible()) {
+ if (!mLauncher.isAppsViewVisible()) {
final Folder openFolder = getOpenFolder();
if (openFolder != null) {
return openFolder.requestFocus(direction, previouslyFocusedRect);
@@ -1841,7 +1852,7 @@ public class Workspace extends SmoothPagedView
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if (!mLauncher.isAllAppsVisible()) {
+ if (!mLauncher.isAppsViewVisible()) {
final Folder openFolder = getOpenFolder();
if (openFolder != null) {
openFolder.addFocusables(views, direction);
@@ -1886,7 +1897,7 @@ public class Workspace extends SmoothPagedView
}
}
- private void updateChildrenLayersEnabled(boolean force) {
+ @Thunk void updateChildrenLayersEnabled(boolean force) {
boolean small = mState == State.OVERVIEW || mIsSwitchingState;
boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
@@ -2050,8 +2061,7 @@ public class Workspace extends SmoothPagedView
// If this is a text view, use its drawable instead
if (v instanceof TextView) {
- TextView tv = (TextView) v;
- Drawable d = tv.getCompoundDrawables()[1];
+ Drawable d = getTextViewIcon((TextView) v);
Rect bounds = getDrawableBounds(d);
bmpWidth = bounds.width();
bmpHeight = bounds.height();
@@ -2069,7 +2079,7 @@ public class Workspace extends SmoothPagedView
}
public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
- int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
+ int[] size = estimateItemSize(info, false);
// The outline is used to visualize where the item will land if dropped
mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
@@ -2210,8 +2220,8 @@ public class Workspace extends SmoothPagedView
private void updateAccessibilityFlags() {
int accessible = mState == State.NORMAL ?
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
- ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+ IMPORTANT_FOR_ACCESSIBILITY_NO :
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
setImportantForAccessibility(accessible);
}
@@ -2254,7 +2264,10 @@ public class Workspace extends SmoothPagedView
float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
- float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
+ // We keep the search bar visible on the workspace and in AllApps now
+ boolean showSearchBar = stateIsNormal ||
+ (mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden);
+ float finalSearchBarAlpha = showSearchBar ? 1f : 0f;
float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
getOverviewModeTranslationY() : 0;
@@ -2331,7 +2344,7 @@ public class Workspace extends SmoothPagedView
}
}
- final View searchBar = mLauncher.getQsbBar();
+ final View searchBar = mLauncher.getOrCreateQsbBar();
final View overviewPanel = mLauncher.getOverviewPanel();
final View hotseat = mLauncher.getHotseat();
final View pageIndicator = getPageIndicator();
@@ -2352,7 +2365,7 @@ public class Workspace extends SmoothPagedView
cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
} else {
if (layerViews != null) {
- layerViews.put(cl, Launcher.BUILD_LAYER);
+ layerViews.put(cl, LauncherStateTransitionAnimation.BUILD_LAYER);
}
if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
LauncherViewPropertyAnimator alphaAnim =
@@ -2404,8 +2417,8 @@ public class Workspace extends SmoothPagedView
if (layerViews != null) {
// If layerViews is not null, we add these views, and indicate that
// the caller can manage layer state.
- layerViews.put(hotseat, Launcher.BUILD_AND_SET_LAYER);
- layerViews.put(overviewPanel, Launcher.BUILD_AND_SET_LAYER);
+ layerViews.put(hotseat, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+ layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
} else {
// Otherwise let the animator handle layer management.
hotseatAlpha.withLayer();
@@ -2434,7 +2447,7 @@ public class Workspace extends SmoothPagedView
if (layerViews != null) {
// If layerViews is not null, we add these views, and indicate that
// the caller can manage layer state.
- layerViews.put(searchBar, Launcher.BUILD_AND_SET_LAYER);
+ layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
} else {
// Otherwise let the animator handle layer management.
searchBarAlpha.withLayer();
@@ -2576,7 +2589,7 @@ public class Workspace extends SmoothPagedView
}
}
- private void onTransitionEnd() {
+ @Thunk void onTransitionEnd() {
mIsSwitchingState = false;
updateChildrenLayersEnabled(false);
showCustomContentIfNecessary();
@@ -2588,6 +2601,19 @@ public class Workspace extends SmoothPagedView
}
/**
+ * Returns the drawable for the given text view.
+ */
+ public static Drawable getTextViewIcon(TextView tv) {
+ final Drawable[] drawables = tv.getCompoundDrawables();
+ for (int i = 0; i < drawables.length; i++) {
+ if (drawables[i] != null) {
+ return drawables[i];
+ }
+ }
+ return null;
+ }
+
+ /**
* Draw the View v into the given Canvas.
*
* @param v the view to draw
@@ -2602,7 +2628,7 @@ public class Workspace extends SmoothPagedView
destCanvas.save();
if (v instanceof TextView) {
- Drawable d = ((TextView) v).getCompoundDrawables()[1];
+ Drawable d = getTextViewIcon((TextView) v);
Rect bounds = getDrawableBounds(d);
clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
@@ -2639,7 +2665,7 @@ public class Workspace extends SmoothPagedView
int padding = expectedPadding.get();
if (v instanceof TextView) {
- Drawable d = ((TextView) v).getCompoundDrawables()[1];
+ Drawable d = getTextViewIcon((TextView) v);
Rect bounds = getDrawableBounds(d);
b = Bitmap.createBitmap(bounds.width() + padding,
bounds.height() + padding, Bitmap.Config.ARGB_8888);
@@ -2700,7 +2726,11 @@ public class Workspace extends SmoothPagedView
return b;
}
- void startDrag(CellLayout.CellInfo cellInfo) {
+ public void startDrag(CellLayout.CellInfo cellInfo) {
+ startDrag(cellInfo, false);
+ }
+
+ public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
View child = cellInfo.cell;
// Make sure the drag was started by a long press as opposed to a long click.
@@ -2713,10 +2743,15 @@ public class Workspace extends SmoothPagedView
CellLayout layout = (CellLayout) child.getParent().getParent();
layout.prepareChildForDrag(child);
- beginDragShared(child, this);
+ beginDragShared(child, this, accessible);
}
- public void beginDragShared(View child, DragSource source) {
+ public void beginDragShared(View child, DragSource source, boolean accessible) {
+ beginDragShared(child, new Point(), source, accessible);
+ }
+
+ public void beginDragShared(View child, Point relativeTouchPos, DragSource source,
+ boolean accessible) {
child.clearFocus();
child.setPressed(false);
@@ -2741,11 +2776,23 @@ public class Workspace extends SmoothPagedView
Point dragVisualizeOffset = null;
Rect dragRect = null;
if (child instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) child;
int iconSize = grid.iconSizePx;
int top = child.getPaddingTop();
int left = (bmpWidth - iconSize) / 2;
int right = left + iconSize;
int bottom = top + iconSize;
+ if (icon.isLayoutHorizontal()) {
+ // If the layout is horizontal, then if we are just picking up the icon, then just
+ // use the child position since the icon is top-left aligned. Otherwise, offset
+ // the drag layer position horizontally so that the icon is under the current
+ // touch position.
+ if (icon.getIcon().getBounds().contains(relativeTouchPos.x, relativeTouchPos.y)) {
+ dragLayerX = Math.round(mTempXY[0]);
+ } else {
+ dragLayerX = Math.round(mTempXY[0] + relativeTouchPos.x - (bmpWidth / 2));
+ }
+ }
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.
@@ -2770,7 +2817,7 @@ public class Workspace extends SmoothPagedView
}
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
@@ -2820,25 +2867,13 @@ public class Workspace extends SmoothPagedView
// Start the drag
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
// Recycle temporary bitmaps
tmpB.recycle();
}
- void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
- 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, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
-
- LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
- cellXY[1]);
- }
-
public boolean transitionStateShouldAllowDrop() {
return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
(mState == State.NORMAL || mState == State.SPRING_LOADED));
@@ -2857,8 +2892,7 @@ public class Workspace extends SmoothPagedView
}
if (!transitionStateShouldAllowDrop()) return false;
- mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
- d.dragView, mDragViewVisualCenter);
+ mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
// We want the point to be mapped to the dragTarget.
if (mLauncher.isHotseatLayout(dropTargetLayout)) {
@@ -3060,9 +3094,7 @@ public class Workspace extends SmoothPagedView
}
public void onDrop(final DragObject d) {
- mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
- mDragViewVisualCenter);
-
+ mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
CellLayout dropTargetLayout = mDropToLayout;
// We want the point to be mapped to the dragTarget.
@@ -3178,7 +3210,8 @@ public class Workspace extends SmoothPagedView
final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
- if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+ if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
+ && !d.accessibleDrag) {
final Runnable addResizeFrame = new Runnable() {
public void run() {
DragLayer dragLayer = mLauncher.getDragLayer();
@@ -3568,38 +3601,6 @@ public class Workspace extends SmoothPagedView
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);
@@ -3624,8 +3625,7 @@ public class Workspace extends SmoothPagedView
// 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);
+ mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
final View child = (mDragInfo == null) ? null : mDragInfo.cell;
// Identify whether we have dragged over a side page
@@ -3700,7 +3700,7 @@ public class Workspace extends SmoothPagedView
mTargetCell[1]);
manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
- targetCellDistance, dragOverView);
+ targetCellDistance, dragOverView, d.accessibleDrag);
boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
@@ -3738,15 +3738,21 @@ public class Workspace extends SmoothPagedView
}
private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
- int[] targetCell, float distance, View dragOverView) {
+ int[] targetCell, float distance, View dragOverView, boolean accessibleDrag) {
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);
+
+ FolderCreationAlarmListener listener = new
+ FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
+
+ if (!accessibleDrag) {
+ mFolderCreationAlarm.setOnAlarmListener(listener);
+ mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+ } else {
+ listener.onAlarm(mFolderCreationAlarm);
+ }
return;
}
@@ -4043,8 +4049,7 @@ public class Workspace extends SmoothPagedView
}
public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
- int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
- widgetInfo.spanY, widgetInfo, false);
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
int visibility = layout.getVisibility();
layout.setVisibility(VISIBLE);
@@ -4115,7 +4120,6 @@ public class Workspace extends SmoothPagedView
// 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);
}
@@ -4199,7 +4203,7 @@ public class Workspace extends SmoothPagedView
*
* pixelX and pixelY should be in the coordinate system of layout
*/
- private int[] findNearestArea(int pixelX, int pixelY,
+ @Thunk int[] findNearestArea(int pixelX, int pixelY,
int spanX, int spanY, CellLayout layout, int[] recycle) {
return layout.findNearestArea(
pixelX, pixelY, spanX, spanY, recycle);
@@ -4236,19 +4240,14 @@ public class Workspace extends SmoothPagedView
removeWorkspaceItem(mDragInfo.cell);
}
} else if (mDragInfo != null) {
- CellLayout cellLayout;
- if (mLauncher.isHotseatLayout(target)) {
- cellLayout = mLauncher.getHotseat().getLayout();
- } else {
- cellLayout = getScreenWithId(mDragInfo.screenId);
- }
- if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
- throw new RuntimeException("Invalid state: cellLayout == null in "
- + "Workspace#onDropCompleted. Please file a bug. ");
- }
+ final CellLayout cellLayout = mLauncher.getCellLayout(
+ mDragInfo.container, mDragInfo.screenId);
if (cellLayout != null) {
cellLayout.onDropChild(mDragInfo.cell);
- }
+ } else if (LauncherAppState.isDogfoodBuild()) {
+ throw new RuntimeException("Invalid state: cellLayout == null in "
+ + "Workspace#onDropCompleted. Please file a bug. ");
+ };
}
if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
&& mDragInfo.cell != null) {
@@ -4258,6 +4257,9 @@ public class Workspace extends SmoothPagedView
mDragInfo = null;
}
+ /**
+ * For opposite operation. See {@link #addInScreen}.
+ */
public void removeWorkspaceItem(View v) {
CellLayout parentCell = getParentCellLayoutForView(v);
if (parentCell != null) {
@@ -4270,11 +4272,13 @@ public class Workspace extends SmoothPagedView
}
}
+ @Override
public void deferCompleteDropAfterUninstallActivity() {
mDeferDropAfterUninstall = true;
}
/// maybe move this into a smaller part
+ @Override
public void onUninstallActivityReturned(boolean success) {
mDeferDropAfterUninstall = false;
mUninstallSuccessful = success;
@@ -4306,88 +4310,6 @@ public class Workspace extends SmoothPagedView
}
}
- ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
- ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
- getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- CellLayout cl = (CellLayout) getChildAt(i);
- getUniqueIntents(cl, uniqueIntents, duplicates, false);
- }
- return uniqueIntents;
- }
-
- void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
- ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
- int count = cl.getShortcutsAndWidgets().getChildCount();
-
- ArrayList<View> children = new ArrayList<View>();
- for (int i = 0; i < count; i++) {
- View v = cl.getShortcutsAndWidgets().getChildAt(i);
- children.add(v);
- }
-
- for (int i = 0; i < count; i++) {
- View v = children.get(i);
- ItemInfo info = (ItemInfo) v.getTag();
- // Null check required as the AllApps button doesn't have an item info
- if (info instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.intent.getComponent();
-
- Uri dataUri = si.intent.getData();
- // If dataUri is not null / empty or if this component isn't one that would
- // have previously showed up in the AllApps list, then this is a widget-type
- // shortcut, so ignore it.
- if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
- continue;
- }
-
- if (!uniqueIntents.contains(cn)) {
- uniqueIntents.add(cn);
- } else {
- if (stripDuplicates) {
- cl.removeViewInLayout(v);
- LauncherModel.deleteItemFromDatabase(mLauncher, si);
- }
- if (duplicates != null) {
- duplicates.add(cn);
- }
- }
- }
- if (v instanceof FolderIcon) {
- FolderIcon fi = (FolderIcon) v;
- ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
- for (int j = 0; j < items.size(); j++) {
- if (items.get(j).getTag() instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
- ComponentName cn = si.intent.getComponent();
-
- Uri dataUri = si.intent.getData();
- // If dataUri is not null / empty or if this component isn't one that would
- // have previously showed up in the AllApps list, then this is a widget-type
- // shortcut, so ignore it.
- if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
- continue;
- }
-
- if (!uniqueIntents.contains(cn)) {
- uniqueIntents.add(cn);
- } else {
- if (stripDuplicates) {
- fi.getFolderInfo().remove(si);
- LauncherModel.deleteItemFromDatabase(mLauncher, si);
- }
- if (duplicates != null) {
- duplicates.add(cn);
- }
- }
- }
- }
- }
- }
- }
-
void saveWorkspaceToDb() {
saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
int count = getChildCount();
@@ -4420,8 +4342,7 @@ public class Workspace extends SmoothPagedView
cellX = hotseat.getCellXFromOrder((int) info.screenId);
cellY = hotseat.getCellYFromOrder((int) info.screenId);
}
- LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
- cellY, false);
+ LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX, cellY);
}
if (v instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) v;
@@ -4879,7 +4800,7 @@ public class Workspace extends SmoothPagedView
updates.contains(info)) {
ShortcutInfo si = (ShortcutInfo) info;
BubbleTextView shortcut = (BubbleTextView) v;
- boolean oldPromiseState = shortcut.getCompoundDrawables()[1]
+ boolean oldPromiseState = getTextViewIcon(shortcut)
instanceof PreloadIconDrawable;
shortcut.applyFromShortcutInfo(si, mIconCache, true,
si.isPromise() != oldPromiseState);
@@ -4914,7 +4835,8 @@ public class Workspace extends SmoothPagedView
if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
// For auto install apps update the icon as well as label.
mIconCache.getTitleAndIcon(shortcutInfo,
- shortcutInfo.promisedIntent, user, true);
+ shortcutInfo.promisedIntent, user,
+ shortcutInfo.shouldUseLowResIcon());
} else {
// Only update the icon for restored apps.
shortcutInfo.updateIcon(mIconCache);
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
new file mode 100644
index 000000000..f890706ff
--- /dev/null
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -0,0 +1,149 @@
+package com.android.launcher3.compat;
+
+import android.content.Context;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+/**
+ * Fallback class to support Alphabetic indexing if not supported by the framework.
+ * TODO(winsonc): disable for non-english locales
+ */
+class BaseAlphabeticIndex {
+
+ private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
+ private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
+
+ public BaseAlphabeticIndex() {}
+
+ /**
+ * Sets the max number of the label buckets in this index.
+ */
+ public void setMaxLabelCount(int count) {
+ // Not currently supported
+ }
+
+ /**
+ * Returns the index of the bucket in which the given string should appear.
+ */
+ protected int getBucketIndex(String s) {
+ if (s.isEmpty()) {
+ return UNKNOWN_BUCKET_INDEX;
+ }
+ int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
+ if (index != -1) {
+ return index;
+ }
+ return UNKNOWN_BUCKET_INDEX;
+ }
+
+ /**
+ * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+ */
+ protected String getBucketLabel(int index) {
+ return BUCKETS.substring(index, index + 1);
+ }
+}
+
+/**
+ * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base alphabetic index.
+ */
+public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
+
+ private Object mAlphabeticIndex;
+ private Method mAddLabelsMethod;
+ private Method mSetMaxLabelCountMethod;
+ private Method mGetBucketIndexMethod;
+ private Method mGetBucketLabelMethod;
+ private boolean mHasValidAlphabeticIndex;
+
+ public AlphabeticIndexCompat(Context context) {
+ super();
+ try {
+ Locale curLocale = context.getResources().getConfiguration().locale;
+ Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
+ Constructor ctor = clazz.getConstructor(Locale.class);
+ mAddLabelsMethod = clazz.getDeclaredMethod("addLabels", Locale.class);
+ mSetMaxLabelCountMethod = clazz.getDeclaredMethod("setMaxLabelCount", int.class);
+ mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
+ mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
+ mAlphabeticIndex = ctor.newInstance(curLocale);
+ try {
+ // Ensure we always have some base English locale buckets
+ if (!curLocale.getLanguage().equals(new Locale("en").getLanguage())) {
+ mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ mHasValidAlphabeticIndex = true;
+ } catch (Exception e) {
+ mHasValidAlphabeticIndex = false;
+ }
+ }
+
+ /**
+ * Sets the max number of the label buckets in this index.
+ * (ICU 51 default is 99)
+ */
+ public void setMaxLabelCount(int count) {
+ if (mHasValidAlphabeticIndex) {
+ try {
+ mSetMaxLabelCountMethod.invoke(mAlphabeticIndex, count);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ super.setMaxLabelCount(count);
+ }
+ }
+
+ /**
+ * Computes the section name for an given string {@param s}.
+ */
+ public String computeSectionName(String s) {
+ String sectionName = getBucketLabel(getBucketIndex(s));
+ if (sectionName.trim().isEmpty() && s.length() > 0) {
+ boolean startsWithDigit = Character.isDigit(Character.codePointAt(s.trim(), 0));
+ if (startsWithDigit) {
+ // Digit section
+ return "#";
+ } else {
+ // Unknown section
+ return "\u2022";
+ }
+ }
+ return sectionName;
+ }
+
+ /**
+ * Returns the index of the bucket in which {@param s} should appear.
+ * Function is synchronized because underlying routine walks an iterator
+ * whose state is maintained inside the index object.
+ */
+ protected int getBucketIndex(String s) {
+ if (mHasValidAlphabeticIndex) {
+ try {
+ return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return super.getBucketIndex(s);
+ }
+
+ /**
+ * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+ */
+ protected String getBucketLabel(int index) {
+ if (mHasValidAlphabeticIndex) {
+ try {
+ return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return super.getBucketLabel(index);
+ }
+}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
index 90a4d1a1f..07ef0efb7 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -17,7 +17,9 @@
package com.android.launcher3.compat;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
public abstract class LauncherActivityInfoCompat {
@@ -32,4 +34,11 @@ public abstract class LauncherActivityInfoCompat {
public abstract ApplicationInfo getApplicationInfo();
public abstract long getFirstInstallTime();
public abstract Drawable getBadgedIcon(int density);
+
+ /**
+ * Creates a LauncherActivityInfoCompat for the primary user.
+ */
+ public static LauncherActivityInfoCompat fromResolveInfo(ResolveInfo info, Context context) {
+ return new LauncherActivityInfoCompatV16(context, info);
+ }
}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
index 1d41a6ff6..ea51aace8 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
@@ -29,13 +29,15 @@ import android.graphics.drawable.Drawable;
public class LauncherActivityInfoCompatV16 extends LauncherActivityInfoCompat {
- private ActivityInfo mActivityInfo;
- private ComponentName mComponentName;
- private PackageManager mPm;
+ private final ResolveInfo mResolveInfo;
+ private final ActivityInfo mActivityInfo;
+ private final ComponentName mComponentName;
+ private final PackageManager mPm;
LauncherActivityInfoCompatV16(Context context, ResolveInfo info) {
super();
- this.mActivityInfo = info.activityInfo;
+ mResolveInfo = info;
+ mActivityInfo = info.activityInfo;
mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
mPm = context.getPackageManager();
}
@@ -49,31 +51,30 @@ public class LauncherActivityInfoCompatV16 extends LauncherActivityInfoCompat {
}
public CharSequence getLabel() {
- return mActivityInfo.loadLabel(mPm);
+ return mResolveInfo.loadLabel(mPm);
}
public Drawable getIcon(int density) {
- Drawable d = null;
- if (mActivityInfo.getIconResource() != 0) {
- Resources resources;
+ int iconRes = mResolveInfo.getIconResource();
+ Resources resources = null;
+ Drawable icon = null;
+ // Get the preferred density icon from the app's resources
+ if (density != 0 && iconRes != 0) {
try {
- resources = mPm.getResourcesForApplication(mActivityInfo.packageName);
- } catch (PackageManager.NameNotFoundException e) {
- resources = null;
- }
- if (resources != null) {
- try {
- d = resources.getDrawableForDensity(mActivityInfo.getIconResource(), density);
- } catch (Resources.NotFoundException e) {
- // Return default icon below.
- }
+ resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (NameNotFoundException | Resources.NotFoundException exc) {
}
}
- if (d == null) {
- Resources resources = Resources.getSystem();
- d = resources.getDrawableForDensity(android.R.mipmap.sym_def_app_icon, density);
+ // Get the default density icon
+ if (icon == null) {
+ icon = mResolveInfo.loadIcon(mPm);
+ }
+ if (icon == null) {
+ resources = Resources.getSystem();
+ icon = resources.getDrawableForDensity(android.R.mipmap.sym_def_app_icon, density);
}
- return d;
+ return icon;
}
public ApplicationInfo getApplicationInfo() {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index e47b9a58d..ac3d252f5 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -31,6 +31,8 @@ import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
+import com.android.launcher3.util.Thunk;
+
import java.util.ArrayList;
import java.util.List;
@@ -139,11 +141,11 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat {
mContext.registerReceiver(mPackageMonitor, filter);
}
- private synchronized List<OnAppsChangedCallbackCompat> getCallbacks() {
+ @Thunk synchronized List<OnAppsChangedCallbackCompat> getCallbacks() {
return new ArrayList<OnAppsChangedCallbackCompat>(mCallbacks);
}
- private class PackageMonitor extends BroadcastReceiver {
+ @Thunk class PackageMonitor extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final UserHandleCompat user = UserHandleCompat.myUserHandle();
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 601f04cea..d6d4b8287 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -26,6 +26,7 @@ import android.util.SparseArray;
import com.android.launcher3.IconCache;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
import java.util.HashSet;
@@ -36,10 +37,10 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements
private static final boolean DEBUG = false;
// All updates to these sets must happen on the {@link #mWorker} thread.
- private final SparseArray<SessionInfo> mPendingReplays = new SparseArray<SessionInfo>();
- private final HashSet<String> mPendingBadgeUpdates = new HashSet<String>();
+ @Thunk final SparseArray<SessionInfo> mPendingReplays = new SparseArray<SessionInfo>();
+ @Thunk final HashSet<String> mPendingBadgeUpdates = new HashSet<String>();
- private final PackageInstaller mInstaller;
+ @Thunk final PackageInstaller mInstaller;
private final IconCache mCache;
private final Handler mWorker;
@@ -82,7 +83,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements
return activePackages;
}
- private void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
+ @Thunk void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
String packageName = info.getAppPackageName();
if (packageName != null) {
mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
@@ -123,7 +124,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat implements
replayUpdates(null);
}
- private void replayUpdates(PackageInstallInfo newInfo) {
+ @Thunk void replayUpdates(PackageInstallInfo newInfo) {
if (DEBUG) Log.d(TAG, "updates resumed");
if (!mResumed || !mBound) {
// Not yet ready
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 1374b4e49..a79d94646 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -43,4 +43,5 @@ public abstract class UserManagerCompat {
public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
public abstract Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user);
public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
+ public abstract long getUserCreationTime(UserHandleCompat user);
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index 32f972e85..ffe698c8b 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -48,4 +48,9 @@ public class UserManagerCompatV16 extends UserManagerCompat {
public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
return label;
}
+
+ @Override
+ public long getUserCreationTime(UserHandleCompat user) {
+ return 0;
+ }
}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 19eeabdcf..884d6fe2a 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -18,21 +18,27 @@
package com.android.launcher3.compat;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
-import android.os.UserManager;
+
+import com.android.launcher3.LauncherAppState;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UserManagerCompatVL extends UserManagerCompatV17 {
+ private static final String USER_CREATION_TIME_KEY = "user_creation_time_";
+
private final PackageManager mPm;
+ private final Context mContext;
UserManagerCompatVL(Context context) {
super(context);
mPm = context.getPackageManager();
+ mContext = context;
}
@Override
@@ -61,5 +67,17 @@ public class UserManagerCompatVL extends UserManagerCompatV17 {
}
return mPm.getUserBadgedLabel(label, user.getUser());
}
+
+ @Override
+ public long getUserCreationTime(UserHandleCompat user) {
+ // TODO: Use system API once available.
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
+ String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
+ if (!prefs.contains(key)) {
+ prefs.edit().putLong(key, System.currentTimeMillis()).apply();
+ }
+ return prefs.getLong(key, 0);
+ }
}
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
new file mode 100644
index 000000000..a84e7df03
--- /dev/null
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2015 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.launcher3.util;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+
+import java.util.Arrays;
+
+/**
+ * Calculates the next item that a {@link KeyEvent} should change the focus to.
+ *<p>
+ * Note, this utility class calculates everything regards to icon index and its (x,y) coordinates.
+ * Currently supports:
+ * <ul>
+ * <li> full matrix of cells that are 1x1
+ * <li> sparse matrix of cells that are 1x1
+ * [ 1][ ][ 2][ ]
+ * [ ][ ][ 3][ ]
+ * [ ][ 4][ ][ ]
+ * [ ][ 5][ 6][ 7]
+ * </ul>
+ * *<p>
+ * For testing, one can use a BT keyboard, or use following adb command.
+ * ex. $ adb shell input keyevent 20 // KEYCODE_DPAD_LEFT
+ */
+public class FocusLogic {
+
+ private static final String TAG = "FocusLogic";
+ private static final boolean DEBUG = false;
+
+ // Item and page index related constant used by {@link #handleKeyEvent}.
+ public static final int NOOP = -1;
+
+ public static final int PREVIOUS_PAGE_RIGHT_COLUMN = -2;
+ public static final int PREVIOUS_PAGE_FIRST_ITEM = -3;
+ public static final int PREVIOUS_PAGE_LAST_ITEM = -4;
+ public static final int PREVIOUS_PAGE_LEFT_COLUMN = -5;
+
+ public static final int CURRENT_PAGE_FIRST_ITEM = -6;
+ public static final int CURRENT_PAGE_LAST_ITEM = -7;
+
+ public static final int NEXT_PAGE_FIRST_ITEM = -8;
+ public static final int NEXT_PAGE_LEFT_COLUMN = -9;
+ public static final int NEXT_PAGE_RIGHT_COLUMN = -10;
+
+ // Matrix related constant.
+ public static final int EMPTY = -1;
+ public static final int PIVOT = 100;
+
+ /**
+ * Returns true only if this utility class handles the key code.
+ */
+ public static boolean shouldConsume(int keyCode) {
+ return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
+ keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
+ keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
+ keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN ||
+ keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL);
+ }
+
+ public static int handleKeyEvent(int keyCode, int cntX, int cntY, int [][] map,
+ int iconIdx, int pageIndex, int pageCount) {
+
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "handleKeyEvent START: cntX=%d, cntY=%d, iconIdx=%d, pageIdx=%d, pageCnt=%d",
+ cntX, cntY, iconIdx, pageIndex, pageCount));
+ }
+
+ DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid()
+ .getDeviceProfile();
+ int newIndex = NOOP;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/);
+ if (!profile.isLayoutRtl && newIndex == NOOP && pageIndex > 0) {
+ newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
+ } else if (profile.isLayoutRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
+ newIndex = NEXT_PAGE_RIGHT_COLUMN;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/);
+ if (!profile.isLayoutRtl && newIndex == NOOP && pageIndex < pageCount - 1) {
+ newIndex = NEXT_PAGE_LEFT_COLUMN;
+ } else if (profile.isLayoutRtl && newIndex == NOOP && pageIndex > 0) {
+ newIndex = PREVIOUS_PAGE_LEFT_COLUMN;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, 1 /*increment*/);
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ newIndex = handleDpadVertical(iconIdx, cntX, cntY, map, -1 /*increment*/);
+ break;
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ newIndex = handleMoveHome();
+ break;
+ case KeyEvent.KEYCODE_MOVE_END:
+ newIndex = handleMoveEnd();
+ break;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ newIndex = handlePageDown(pageIndex, pageCount);
+ break;
+ case KeyEvent.KEYCODE_PAGE_UP:
+ newIndex = handlePageUp(pageIndex);
+ break;
+ default:
+ break;
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, String.format("handleKeyEvent FINISH: index [%d -> %s]",
+ iconIdx, getStringIndex(newIndex)));
+ }
+ return newIndex;
+ }
+
+ /**
+ * Returns a matrix of size (m x n) that has been initialized with {@link #EMPTY}.
+ *
+ * @param m number of columns in the matrix
+ * @param n number of rows in the matrix
+ */
+ // TODO: get rid of dynamic matrix creation.
+ private static int[][] createFullMatrix(int m, int n) {
+ int[][] matrix = new int [m][n];
+
+ for (int i=0; i < m;i++) {
+ Arrays.fill(matrix[i], EMPTY);
+ }
+ return matrix;
+ }
+
+ /**
+ * Returns a matrix of size same as the {@link CellLayout} dimension that is initialized with the
+ * index of the child view.
+ */
+ // TODO: get rid of the dynamic matrix creation
+ public static int[][] createSparseMatrix(CellLayout layout) {
+ ShortcutAndWidgetContainer parent = layout.getShortcutsAndWidgets();
+ final int m = layout.getCountX();
+ final int n = layout.getCountY();
+ final boolean invert = parent.invertLayoutHorizontally();
+
+ int[][] matrix = createFullMatrix(m, n);
+
+ // Iterate thru the children.
+ for (int i = 0; i < parent.getChildCount(); i++ ) {
+ int cx = ((CellLayout.LayoutParams) parent.getChildAt(i).getLayoutParams()).cellX;
+ int cy = ((CellLayout.LayoutParams) parent.getChildAt(i).getLayoutParams()).cellY;
+ matrix[invert ? (m - cx - 1) : cx][cy] = i;
+ }
+ if (DEBUG) {
+ printMatrix(matrix);
+ }
+ return matrix;
+ }
+
+ /**
+ * Creates a sparse matrix that merges the icon and hotseat view group using the cell layout.
+ * The size of the returning matrix is [icon column count x (icon + hotseat row count)]
+ * in portrait orientation. In landscape, [(icon + hotseat) column count x (icon row count)]
+ */
+ // TODO: get rid of the dynamic matrix creation
+ public static int[][] createSparseMatrix(CellLayout iconLayout, CellLayout hotseatLayout,
+ boolean isHorizontal, int allappsiconRank, boolean includeAllappsicon) {
+
+ ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+ ViewGroup hotseatParent = hotseatLayout.getShortcutsAndWidgets();
+
+ int m, n;
+ if (isHorizontal) {
+ m = iconLayout.getCountX();
+ n = iconLayout.getCountY() + hotseatLayout.getCountY();
+ } else {
+ m = iconLayout.getCountX() + hotseatLayout.getCountX();
+ n = iconLayout.getCountY();
+ }
+ int[][] matrix = createFullMatrix(m, n);
+
+ // Iterate thru the children of the top parent.
+ for (int i = 0; i < iconParent.getChildCount(); i++) {
+ int cx = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellX;
+ int cy = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellY;
+ matrix[cx][cy] = i;
+ }
+
+ // Iterate thru the children of the bottom parent
+ // The hotseat view group contains one more item than iconLayout column count.
+ // If {@param allappsiconRank} not negative, then the last icon in the hotseat
+ // is truncated. If it is negative, then all apps icon index is not inserted.
+ for(int i = hotseatParent.getChildCount() - 1; i >= (includeAllappsicon ? 0 : 1); i--) {
+ int delta = 0;
+ if (isHorizontal) {
+ int cx = ((CellLayout.LayoutParams)
+ hotseatParent.getChildAt(i).getLayoutParams()).cellX;
+ if ((includeAllappsicon && cx >= allappsiconRank) ||
+ (!includeAllappsicon && cx > allappsiconRank)) {
+ delta = -1;
+ }
+ matrix[cx + delta][iconLayout.getCountY()] = iconParent.getChildCount() + i;
+ } else {
+ int cy = ((CellLayout.LayoutParams)
+ hotseatParent.getChildAt(i).getLayoutParams()).cellY;
+ if ((includeAllappsicon && cy >= allappsiconRank) ||
+ (!includeAllappsicon && cy > allappsiconRank)) {
+ delta = -1;
+ }
+ matrix[iconLayout.getCountX()][cy + delta] = iconParent.getChildCount() + i;
+ }
+ }
+ if (DEBUG) {
+ printMatrix(matrix);
+ }
+ return matrix;
+ }
+
+ /**
+ * Creates a sparse matrix that merges the icon of previous/next page and last column of
+ * current page. When left key is triggered on the leftmost column, sparse matrix is created
+ * that combines previous page matrix and an extra column on the right. Likewise, when right
+ * key is triggered on the rightmost column, sparse matrix is created that combines this column
+ * on the 0th column and the next page matrix.
+ *
+ * @param pivotX x coordinate of the focused item in the current page
+ * @param pivotY y coordinate of the focused item in the current page
+ */
+ // TODO: get rid of the dynamic matrix creation
+ public static int[][] createSparseMatrix(CellLayout iconLayout, int pivotX, int pivotY) {
+
+ ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+ int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY());
+
+ // Iterate thru the children of the top parent.
+ for (int i = 0; i < iconParent.getChildCount(); i++) {
+ int cx = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellX;
+ int cy = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellY;
+ if (pivotX < 0) {
+ matrix[cx - pivotX][cy] = i;
+ } else {
+ matrix[cx][cy] = i;
+ }
+ }
+
+ if (pivotX < 0) {
+ matrix[0][pivotY] = PIVOT;
+ } else {
+ matrix[pivotX][pivotY] = PIVOT;
+ }
+ if (DEBUG) {
+ printMatrix(matrix);
+ }
+ return matrix;
+ }
+
+ //
+ // key event handling methods.
+ //
+
+ /**
+ * Calculates icon that has is closest to the horizontal axis in reference to the cur icon.
+ *
+ * Example of the check order for KEYCODE_DPAD_RIGHT:
+ * [ ][ ][13][14][15]
+ * [ ][ 6][ 8][10][12]
+ * [ X][ 1][ 2][ 3][ 4]
+ * [ ][ 5][ 7][ 9][11]
+ */
+ // TODO: add unit tests to verify all permutation.
+ private static int handleDpadHorizontal(int iconIdx, int cntX, int cntY,
+ int[][] matrix, int increment) {
+ if(matrix == null) {
+ throw new IllegalStateException("Dpad navigation requires a matrix.");
+ }
+ int newIconIndex = NOOP;
+
+ int xPos = -1;
+ int yPos = -1;
+ // Figure out the location of the icon.
+ for (int i = 0; i < cntX; i++) {
+ for (int j = 0; j < cntY; j++) {
+ if (matrix[i][j] == iconIdx) {
+ xPos = i;
+ yPos = j;
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.v(TAG, String.format("\thandleDpadHorizontal: \t[x, y]=[%d, %d] iconIndex=%d",
+ xPos, yPos, iconIdx));
+ }
+
+ // Rule1: check first in the horizontal direction
+ for (int i = xPos + increment; 0 <= i && i < cntX; i = i + increment) {
+ if ((newIconIndex = inspectMatrix(i, yPos, cntX, cntY, matrix)) != NOOP) {
+ return newIconIndex;
+ }
+ }
+
+ // Rule2: check (x1-n, yPos + increment), (x1-n, yPos - increment)
+ // (x2-n, yPos + 2*increment), (x2-n, yPos - 2*increment)
+ int nextYPos1;
+ int nextYPos2;
+ int i = -1;
+ for (int coeff = 1; coeff < cntY; coeff++) {
+ nextYPos1 = yPos + coeff * increment;
+ nextYPos2 = yPos - coeff * increment;
+ for (i = xPos + increment * coeff; 0 <= i && i < cntX; i = i + increment) {
+ if ((newIconIndex = inspectMatrix(i, nextYPos1, cntX, cntY, matrix)) != NOOP) {
+ return newIconIndex;
+ }
+ if ((newIconIndex = inspectMatrix(i, nextYPos2, cntX, cntY, matrix)) != NOOP) {
+ return newIconIndex;
+ }
+ }
+ }
+ return newIconIndex;
+ }
+
+ /**
+ * Calculates icon that is closest to the vertical axis in reference to the current icon.
+ *
+ * Example of the check order for KEYCODE_DPAD_DOWN:
+ * [ ][ ][ ][ X][ ][ ][ ]
+ * [ ][ ][ 5][ 1][ 4][ ][ ]
+ * [ ][10][ 7][ 2][ 6][ 9][ ]
+ * [14][12][ 9][ 3][ 8][11][13]
+ */
+ // TODO: add unit tests to verify all permutation.
+ private static int handleDpadVertical(int iconIndex, int cntX, int cntY,
+ int [][] matrix, int increment) {
+ int newIconIndex = NOOP;
+ if(matrix == null) {
+ throw new IllegalStateException("Dpad navigation requires a matrix.");
+ }
+
+ int xPos = -1;
+ int yPos = -1;
+ // Figure out the location of the icon.
+ for (int i = 0; i< cntX; i++) {
+ for (int j = 0; j < cntY; j++) {
+ if (matrix[i][j] == iconIndex) {
+ xPos = i;
+ yPos = j;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, String.format("\thandleDpadVertical: \t[x, y]=[%d, %d] iconIndex=%d",
+ xPos, yPos, iconIndex));
+ }
+
+ // Rule1: check first in the dpad direction
+ for (int j = yPos + increment; 0 <= j && j <cntY && 0 <= j; j = j + increment) {
+ if ((newIconIndex = inspectMatrix(xPos, j, cntX, cntY, matrix)) != NOOP) {
+ return newIconIndex;
+ }
+ }
+
+ // Rule2: check (xPos + increment, y_(1-n)), (xPos - increment, y_(1-n))
+ // (xPos + 2*increment, y_(2-n))), (xPos - 2*increment, y_(2-n))
+ int nextXPos1;
+ int nextXPos2;
+ int j = -1;
+ for (int coeff = 1; coeff < cntX; coeff++) {
+ nextXPos1 = xPos + coeff * increment;
+ nextXPos2 = xPos - coeff * increment;
+ for (j = yPos + increment * coeff; 0 <= j && j < cntY; j = j + increment) {
+ if ((newIconIndex = inspectMatrix(nextXPos1, j, cntX, cntY, matrix)) != NOOP) {
+ return newIconIndex;
+ }
+ if ((newIconIndex = inspectMatrix(nextXPos2, j, cntX, cntY, matrix)) != NOOP) {
+ return newIconIndex;
+ }
+ }
+ }
+ return newIconIndex;
+ }
+
+ private static int handleMoveHome() {
+ return CURRENT_PAGE_FIRST_ITEM;
+ }
+
+ private static int handleMoveEnd() {
+ return CURRENT_PAGE_LAST_ITEM;
+ }
+
+ private static int handlePageDown(int pageIndex, int pageCount) {
+ if (pageIndex < pageCount -1) {
+ return NEXT_PAGE_FIRST_ITEM;
+ }
+ return CURRENT_PAGE_LAST_ITEM;
+ }
+
+ private static int handlePageUp(int pageIndex) {
+ if (pageIndex > 0) {
+ return PREVIOUS_PAGE_FIRST_ITEM;
+ } else {
+ return CURRENT_PAGE_FIRST_ITEM;
+ }
+ }
+
+ //
+ // Helper methods.
+ //
+
+ private static boolean isValid(int xPos, int yPos, int countX, int countY) {
+ return (0 <= xPos && xPos < countX && 0 <= yPos && yPos < countY);
+ }
+
+ private static int inspectMatrix(int x, int y, int cntX, int cntY, int[][] matrix) {
+ int newIconIndex = NOOP;
+ if (isValid(x, y, cntX, cntY)) {
+ if (matrix[x][y] != -1) {
+ newIconIndex = matrix[x][y];
+ if (DEBUG) {
+ Log.v(TAG, String.format("\t\tinspect: \t[x, y]=[%d, %d] %d",
+ x, y, matrix[x][y]));
+ }
+ return newIconIndex;
+ }
+ }
+ return newIconIndex;
+ }
+
+ /**
+ * Only used for debugging.
+ */
+ private static String getStringIndex(int index) {
+ switch(index) {
+ case NOOP: return "NOOP";
+ case PREVIOUS_PAGE_FIRST_ITEM: return "PREVIOUS_PAGE_FIRST";
+ case PREVIOUS_PAGE_LAST_ITEM: return "PREVIOUS_PAGE_LAST";
+ case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
+ case CURRENT_PAGE_FIRST_ITEM: return "CURRENT_PAGE_FIRST";
+ case CURRENT_PAGE_LAST_ITEM: return "CURRENT_PAGE_LAST";
+ case NEXT_PAGE_FIRST_ITEM: return "NEXT_PAGE_FIRST";
+ case NEXT_PAGE_LEFT_COLUMN: return "NEXT_PAGE_LEFT_COLUMN";
+ default:
+ return Integer.toString(index);
+ }
+ }
+
+ /**
+ * Only used for debugging.
+ */
+ private static void printMatrix(int[][] matrix) {
+ Log.v(TAG, "\tprintMap:");
+ int m = matrix.length;
+ int n = matrix[0].length;
+
+ for (int j=0; j < n; j++) {
+ String colY = "\t\t";
+ for (int i=0; i < m; i++) {
+ colY += String.format("%3d",matrix[i][j]);
+ }
+ Log.v(TAG, colY);
+ }
+ }
+
+ /**
+ * @param edgeColumn the column of the new icon. either {@link #NEXT_PAGE_LEFT_COLUMN} or
+ * {@link #NEXT_PAGE_RIGHT_COLUMN}
+ * @return the view adjacent to {@param oldView} in the {@param nextPage}.
+ */
+ public static View getAdjacentChildInNextPage(
+ ShortcutAndWidgetContainer nextPage, View oldView, int edgeColumn) {
+ final int newRow = ((CellLayout.LayoutParams) oldView.getLayoutParams()).cellY;
+
+ int column = (edgeColumn == NEXT_PAGE_LEFT_COLUMN) ^ nextPage.invertLayoutHorizontally()
+ ? 0 : (((CellLayout) nextPage.getParent()).getCountX() - 1);
+
+ for (; column >= 0; column--) {
+ for (int row = newRow; row >= 0; row--) {
+ View newView = nextPage.getChildAt(column, row);
+ if (newView != null) {
+ return newView;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
new file mode 100644
index 000000000..cefa71c39
--- /dev/null
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -0,0 +1,277 @@
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Handles addition of app shortcuts for managed profiles.
+ * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
+ */
+public class ManagedProfileHeuristic {
+
+ private static final String TAG = "ManagedProfileHeuristic";
+
+ /**
+ * Maintain a set of packages installed per user.
+ */
+ private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";
+
+ private static final String USER_FOLDER_ID_PREFIX = "user_folder_";
+
+ /**
+ * Duration (in milliseconds) for which app shortcuts will be added to work folder.
+ */
+ private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
+
+ public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
+ if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+ return new ManagedProfileHeuristic(context, user);
+ }
+ return null;
+ }
+
+ private final Context mContext;
+ private final UserHandleCompat mUser;
+ private final LauncherModel mModel;
+
+ private final SharedPreferences mPrefs;
+ private final long mUserSerial;
+ private final long mUserCreationTime;
+ private final String mPackageSetKey;
+
+ private ArrayList<ItemInfo> mHomescreenApps;
+ private ArrayList<ItemInfo> mWorkFolderApps;
+
+ private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
+ mContext = context;
+ mUser = user;
+ mModel = LauncherAppState.getInstance().getModel();
+
+ UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+ mUserSerial = userManager.getSerialNumberForUser(user);
+ mUserCreationTime = userManager.getUserCreationTime(user);
+ mPackageSetKey = INSTALLED_PACKAGES_PREFIX + mUserSerial;
+
+ mPrefs = mContext.getSharedPreferences(LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
+ Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Checks the list of user apps and adds icons for newly installed apps on the homescreen or
+ * workfolder.
+ */
+ public void processUserApps(List<LauncherActivityInfoCompat> apps) {
+ mHomescreenApps = new ArrayList<ItemInfo>();
+ mWorkFolderApps = new ArrayList<ItemInfo>();
+ HashSet<String> packageSet = getPackageSet();
+ boolean newPackageAdded = false;
+
+ for (LauncherActivityInfoCompat info : apps) {
+ String packageName = info.getComponentName().getPackageName();
+ if (!packageSet.contains(packageName)) {
+ packageSet.add(packageName);
+ newPackageAdded = true;
+
+ try {
+ PackageInfo pkgInfo = mContext.getPackageManager()
+ .getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+ markForAddition(info, pkgInfo.firstInstallTime);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Unknown package " + packageName, e);
+ }
+ }
+ }
+
+ if (newPackageAdded) {
+ mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+ finalizeAdditions();
+ }
+ }
+
+ private void markForAddition(LauncherActivityInfoCompat info, long installTime) {
+ ArrayList<ItemInfo> targetList =
+ (installTime <= mUserCreationTime + AUTO_ADD_TO_FOLDER_DURATION) ?
+ mWorkFolderApps : mHomescreenApps;
+ targetList.add(ShortcutInfo.fromActivityInfo(info, mContext));
+ }
+
+ /**
+ * Adds and binds shortcuts marked to be added to the work folder.
+ */
+ private void finalizeWorkFolder() {
+ if (mWorkFolderApps.isEmpty()) {
+ return;
+ }
+
+ // Try to get a work folder.
+ String folderIdKey = USER_FOLDER_ID_PREFIX + mUserSerial;
+ if (mPrefs.contains(folderIdKey)) {
+ long folderId = mPrefs.getLong(folderIdKey, 0);
+ final FolderInfo workFolder = mModel.findFolderById(folderId);
+
+ if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
+ // Could not get a work folder. Add all the icons to homescreen.
+ mHomescreenApps.addAll(mWorkFolderApps);
+ return;
+ }
+ saveWorkFolderShortcuts(folderId, workFolder.contents.size());
+
+ final ArrayList<ItemInfo> shortcuts = mWorkFolderApps;
+ // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
+ new MainThreadExecutor().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ for (ItemInfo info : shortcuts) {
+ workFolder.add((ShortcutInfo) info);
+ }
+ }
+ });
+ } else {
+ // Create a new folder.
+ final FolderInfo workFolder = new FolderInfo();
+ workFolder.title = mContext.getText(R.string.work_folder_name);
+ workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
+
+ // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
+ for (ItemInfo info : mWorkFolderApps) {
+ workFolder.add((ShortcutInfo) info);
+ }
+
+ // Add the item to home screen and DB. This also generates an item id synchronously.
+ ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+ itemList.add(workFolder);
+ mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+ mPrefs.edit().putLong(USER_FOLDER_ID_PREFIX + mUserSerial, workFolder.id).apply();
+
+ saveWorkFolderShortcuts(workFolder.id, 0);
+ }
+ }
+
+ /**
+ * Add work folder shortcuts to the DB.
+ */
+ private void saveWorkFolderShortcuts(long workFolderId, int startingRank) {
+ for (ItemInfo info : mWorkFolderApps) {
+ info.rank = startingRank++;
+ LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
+ }
+ }
+
+ /**
+ * Adds and binds all shortcuts marked for addition.
+ */
+ private void finalizeAdditions() {
+ finalizeWorkFolder();
+
+ if (!mHomescreenApps.isEmpty()) {
+ mModel.addAndBindAddedWorkspaceItems(mContext, mHomescreenApps);
+ }
+ }
+
+ /**
+ * Updates the list of installed apps and adds any new icons on homescreen or work folder.
+ */
+ public void processPackageAdd(String[] packages) {
+ mHomescreenApps = new ArrayList<ItemInfo>();
+ mWorkFolderApps = new ArrayList<ItemInfo>();
+ HashSet<String> packageSet = getPackageSet();
+ boolean newPackageAdded = false;
+ long installTime = System.currentTimeMillis();
+ LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
+
+ for (String packageName : packages) {
+ if (!packageSet.contains(packageName)) {
+ packageSet.add(packageName);
+ newPackageAdded = true;
+
+ List<LauncherActivityInfoCompat> activities =
+ launcherApps.getActivityList(packageName, mUser);
+ if (!activities.isEmpty()) {
+ markForAddition(activities.get(0), installTime);
+ }
+ }
+ }
+
+ if (newPackageAdded) {
+ mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+ finalizeAdditions();
+ }
+ }
+
+ /**
+ * Updates the list of installed packages for the user.
+ */
+ public void processPackageRemoved(String[] packages) {
+ HashSet<String> packageSet = getPackageSet();
+ boolean packageRemoved = false;
+
+ for (String packageName : packages) {
+ if (packageSet.remove(packageName)) {
+ packageRemoved = true;
+ }
+ }
+
+ if (packageRemoved) {
+ mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private HashSet<String> getPackageSet() {
+ return new HashSet<String>(mPrefs.getStringSet(mPackageSetKey, Collections.EMPTY_SET));
+ }
+
+ /**
+ * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
+ */
+ public static void processAllUsers(List<UserHandleCompat> users, Context context) {
+ if (!Utilities.isLmpOrAbove()) {
+ return;
+ }
+ UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+ HashSet<String> validKeys = new HashSet<String>();
+ for (UserHandleCompat user : users) {
+ addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
+ }
+
+ SharedPreferences prefs = context.getSharedPreferences(
+ LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ for (String key : prefs.getAll().keySet()) {
+ if (!validKeys.contains(key)) {
+ editor.remove(key);
+ }
+ }
+ editor.apply();
+ }
+
+ private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) {
+ keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial);
+ keysOut.add(USER_FOLDER_ID_PREFIX + userSerial);
+ }
+}
diff --git a/src/com/android/launcher3/util/Thunk.java b/src/com/android/launcher3/util/Thunk.java
new file mode 100644
index 000000000..de350b068
--- /dev/null
+++ b/src/com/android/launcher3/util/Thunk.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.launcher3.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the given field or method has package visibility solely to prevent the creation
+ * of a synthetic method. In practice, you should treat this field/method as if it were private.
+ * <p>
+ *
+ * When a private method is called from an inner class, the Java compiler generates a simple
+ * package private shim method that the class generated from the inner class can call. This results
+ * in unnecessary bloat and runtime method call overhead. It also gets us closer to the dex method
+ * count limit.
+ * <p>
+ *
+ * If you'd like to see warnings for these synthetic methods in eclipse, turn on:
+ * Window > Preferences > Java > Compiler > Errors/Warnings > "Access to a non-accessible member
+ * of an enclosing type".
+ * <p>
+ *
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE})
+public @interface Thunk { } \ No newline at end of file
diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
new file mode 100644
index 000000000..53b2acd84
--- /dev/null
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 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.launcher3.util;
+
+import android.annotation.TargetApi;
+import android.app.WallpaperManager;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.WindowManager;
+
+/**
+ * Utility methods for wallpaper management.
+ */
+public final class WallpaperUtils {
+
+ public static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
+ public static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
+ public static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+ public static void suggestWallpaperDimension(Resources res,
+ final SharedPreferences sharedPrefs,
+ WindowManager windowManager,
+ final WallpaperManager wallpaperManager, boolean fallBackToDefaults) {
+ final Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(res, windowManager);
+ // If we have saved a wallpaper width/height, use that instead
+
+ int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
+ int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1);
+
+ if (savedWidth == -1 || savedHeight == -1) {
+ if (!fallBackToDefaults) {
+ return;
+ } else {
+ savedWidth = defaultWallpaperSize.x;
+ savedHeight = defaultWallpaperSize.y;
+ }
+ }
+
+ if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
+ savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
+ wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+ }
+ }
+
+ /**
+ * As a ratio of screen height, the total distance we want the parallax effect to span
+ * horizontally
+ */
+ public static 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;
+ }
+
+ private static Point sDefaultWallpaperSize;
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
+ if (sDefaultWallpaperSize == null) {
+ Point minDims = new Point();
+ Point maxDims = new Point();
+ windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+
+ int maxDim = Math.max(maxDims.x, maxDims.y);
+ int minDim = Math.max(minDims.x, minDims.y);
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ Point realSize = new Point();
+ windowManager.getDefaultDisplay().getRealSize(realSize);
+ maxDim = Math.max(realSize.x, realSize.y);
+ minDim = Math.min(realSize.x, realSize.y);
+ }
+
+ // We need to ensure that there is enough extra space in the wallpaper
+ // for the intended parallax effects
+ final int defaultWidth, defaultHeight;
+ if (res.getConfiguration().smallestScreenWidthDp >= 720) {
+ defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+ defaultHeight = maxDim;
+ } else {
+ defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+ defaultHeight = maxDim;
+ }
+ sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight);
+ }
+ return sDefaultWallpaperSize;
+ }
+}
diff --git a/src/com/android/launcher3/widget/PackageItemInfo.java b/src/com/android/launcher3/widget/PackageItemInfo.java
new file mode 100644
index 000000000..1a1de55c2
--- /dev/null
+++ b/src/com/android/launcher3/widget/PackageItemInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 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.launcher3.widget;
+
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+
+import com.android.launcher3.ItemInfo;
+
+import java.util.Arrays;
+
+/**
+ * Represents a {@link Package} in the widget tray section.
+ */
+public class PackageItemInfo extends ItemInfo {
+ private static final String TAG = "PackageInfo";
+
+ /**
+ * A bitmap version of the application icon.
+ */
+ public Bitmap iconBitmap;
+
+ /**
+ * Indicates whether we're using a low res icon
+ */
+ public boolean usingLowResIcon;
+
+ public String packageName;
+
+ int flags = 0;
+
+ PackageItemInfo(String packageName) {
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageItemInfo(title=" + title.toString() + " id=" + this.id
+ + " type=" + this.itemType + " container=" + this.container
+ + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
+ + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+ + " user=" + user + ")";
+ }
+}
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
new file mode 100644
index 000000000..a56985083
--- /dev/null
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 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.launcher3.widget;
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PendingAddItemInfo;
+
+/**
+ * Meta data used for late binding of the short cuts.
+ *
+ * @see {@link PendingAddItemInfo}
+ */
+public class PendingAddShortcutInfo extends PendingAddItemInfo {
+
+ ActivityInfo activityInfo;
+
+ public PendingAddShortcutInfo(ActivityInfo activityInfo) {
+ this.activityInfo = activityInfo;
+ componentName = new ComponentName(activityInfo.packageName, activityInfo.name);
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PendingAddShortcutInfo package=%s, name=%s",
+ activityInfo.packageName, activityInfo.name);
+ }
+}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
new file mode 100644
index 000000000..db1699818
--- /dev/null
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 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.launcher3.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PendingAddItemInfo;
+
+/**
+ * Meta data used for late binding of {@link LauncherAppWidgetProviderInfo}.
+ *
+ * @see {@link PendingAddItemInfo}
+ */
+public class PendingAddWidgetInfo extends PendingAddItemInfo {
+ public int minWidth;
+ public int minHeight;
+ public int minResizeWidth;
+ public int minResizeHeight;
+ public int previewImage;
+ public int icon;
+ public LauncherAppWidgetProviderInfo info;
+ public AppWidgetHostView boundWidget;
+ public Bundle bindOptions = null;
+
+ public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i, Parcelable data) {
+ if (i.isCustomWidget) {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+ } else {
+ 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;
+
+ spanX = i.spanX;
+ spanY = i.spanY;
+ minSpanX = i.minSpanX;
+ minSpanY = i.minSpanY;
+ }
+
+ public boolean isCustomWidget() {
+ return itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+ }
+
+ // 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;
+ componentName = copy.componentName;
+ itemType = copy.itemType;
+ spanX = copy.spanX;
+ spanY = copy.spanY;
+ minSpanX = copy.minSpanX;
+ minSpanY = copy.minSpanY;
+ bindOptions = copy.bindOptions == null ? null : (Bundle) copy.bindOptions.clone();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PendingAddWidgetInfo package=%s, name=%s",
+ componentName.getPackageName(), componentName.getShortClassName());
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/widget/WidgetCell.java
index 107069b78..1ae75c3cc 100644
--- a/src/com/android/launcher3/PagedViewWidget.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,59 +14,78 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.widget;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnLayoutChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
import com.android.launcher3.compat.AppWidgetManagerCompat;
/**
- * The linear layout used strictly for the widget/wallpaper tab of the customization tray
+ * The linear layout used strictly for the widget tray.
*/
-public class PagedViewWidget extends LinearLayout {
- static final String TAG = "PagedViewWidgetLayout";
+public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
- private static boolean sDeletePreviewsWhenDetachedFromWindow = true;
- private static boolean sRecyclePreviewsWhenDetachedFromWindow = true;
+ private static final String TAG = "WidgetCell";
+ private static final boolean DEBUG = false;
+
+ private static final int FADE_IN_DURATION_MS = 70;
+ private int mPresetPreviewSize;
+
+ private static WidgetCell sShortpressTarget = null;
- private String mDimensionsFormatString;
- CheckForShortPress mPendingCheckForShortPress = null;
- ShortPressListener mShortPressListener = null;
- boolean mShortPressTriggered = false;
- static PagedViewWidget sShortpressTarget = null;
- boolean mIsAppWidget;
private final Rect mOriginalImagePadding = new Rect();
+
+ private String mDimensionsFormatString;
+ private CheckForShortPress mPendingCheckForShortPress = null;
+ private ShortPressListener mShortPressListener = null;
+ private boolean mShortPressTriggered = false;
+ private boolean mIsAppWidget;
private Object mInfo;
+
private WidgetPreviewLoader mWidgetPreviewLoader;
+ private PreviewLoadRequest mActiveRequest;
- public PagedViewWidget(Context context) {
+ public WidgetCell(Context context) {
this(context, null);
}
- public PagedViewWidget(Context context, AttributeSet attrs) {
+ public WidgetCell(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public PagedViewWidget(Context context, AttributeSet attrs, int defStyle) {
+ public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources r = context.getResources();
mDimensionsFormatString = r.getString(R.string.widget_dims_format);
+ mPresetPreviewSize = r.getDimensionPixelSize(R.dimen.widget_preview_size);
setWillNotDraw(false);
setClipToPadding(false);
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
+
}
@Override
@@ -92,29 +111,36 @@ public class PagedViewWidget extends LinearLayout {
}
}
- public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) {
- sDeletePreviewsWhenDetachedFromWindow = value;
- }
-
- public static void setRecyclePreviewsWhenDetachedFromWindow(boolean value) {
- sRecyclePreviewsWhenDetachedFromWindow = value;
- }
-
@Override
protected void onDetachedFromWindow() {
+ if (DEBUG) {
+ Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString()));
+ }
super.onDetachedFromWindow();
+ deletePreview(false);
+ }
- if (sDeletePreviewsWhenDetachedFromWindow) {
+ public void reset() {
+ ImageView image = (ImageView) findViewById(R.id.widget_preview);
+ final TextView name = (TextView) findViewById(R.id.widget_name);
+ final TextView dims = (TextView) findViewById(R.id.widget_dims);
+ image.setImageDrawable(null);
+ name.setText(null);
+ dims.setText(null);
+ }
+
+ public void deletePreview(boolean recycleImage) {
+ if (recycleImage) {
final ImageView image = (ImageView) findViewById(R.id.widget_preview);
if (image != null) {
- FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable();
- if (sRecyclePreviewsWhenDetachedFromWindow &&
- mInfo != null && preview != null && preview.getBitmap() != null) {
- mWidgetPreviewLoader.recycleBitmap(mInfo, preview.getBitmap());
- }
image.setImageDrawable(null);
}
}
+
+ if (mActiveRequest != null) {
+ mActiveRequest.cancel(recycleImage);
+ mActiveRequest = null;
+ }
}
public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
@@ -156,14 +182,19 @@ public class PagedViewWidget extends LinearLayout {
public int[] getPreviewSize() {
final ImageView i = (ImageView) findViewById(R.id.widget_preview);
int[] maxSize = new int[2];
- maxSize[0] = i.getWidth() - mOriginalImagePadding.left - mOriginalImagePadding.right;
- maxSize[1] = i.getHeight() - mOriginalImagePadding.top;
+ maxSize[0] = mPresetPreviewSize;
+ maxSize[1] = mPresetPreviewSize;
return maxSize;
}
- void applyPreview(FastBitmapDrawable preview, int index) {
- final PagedViewWidgetImageView image =
- (PagedViewWidgetImageView) findViewById(R.id.widget_preview);
+ public void applyPreview(Bitmap bitmap) {
+ FastBitmapDrawable preview = new FastBitmapDrawable(bitmap);
+ final WidgetImageView image =
+ (WidgetImageView) findViewById(R.id.widget_preview);
+ if (DEBUG) {
+ Log.d(TAG, String.format("[tag=%s] applyPreview preview: %s",
+ getTagToString(), preview));
+ }
if (preview != null) {
image.mAllowRequestLayout = false;
image.setImageDrawable(preview);
@@ -176,8 +207,10 @@ public class PagedViewWidget extends LinearLayout {
mOriginalImagePadding.right,
mOriginalImagePadding.bottom);
}
- image.setAlpha(1f);
+ image.setAlpha(0f);
+ image.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
image.mAllowRequestLayout = true;
+ image.requestLayout();
}
}
@@ -194,8 +227,8 @@ public class PagedViewWidget extends LinearLayout {
public void run() {
if (sShortpressTarget != null) return;
if (mShortPressListener != null) {
- mShortPressListener.onShortPress(PagedViewWidget.this);
- sShortpressTarget = PagedViewWidget.this;
+ mShortPressListener.onShortPress(WidgetCell.this);
+ sShortpressTarget = WidgetCell.this;
}
mShortPressTriggered = true;
}
@@ -222,7 +255,7 @@ public class PagedViewWidget extends LinearLayout {
removeShortPressCallback();
if (mShortPressTriggered) {
if (mShortPressListener != null) {
- mShortPressListener.cleanUpShortPress(PagedViewWidget.this);
+ mShortPressListener.cleanUpShortPress(WidgetCell.this);
}
mShortPressTriggered = false;
}
@@ -259,4 +292,54 @@ public class PagedViewWidget extends LinearLayout {
// we just always mark the touch event as handled.
return true;
}
+
+ public void ensurePreview() {
+ if (mActiveRequest != null) {
+ return;
+ }
+ int[] size = getPreviewSize();
+ if (DEBUG) {
+ Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
+ getTagToString(), size[0], size[1]));
+ }
+
+ if (size[0] <= 0 || size[1] <= 0) {
+ addOnLayoutChangeListener(this);
+ return;
+ }
+ Bitmap[] immediateResult = new Bitmap[1];
+ mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this,
+ immediateResult);
+ if (immediateResult[0] != null) {
+ applyPreview(immediateResult[0]);
+ }
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ removeOnLayoutChangeListener(this);
+ ensurePreview();
+ }
+
+ public int getActualItemWidth() {
+ ItemInfo info = (ItemInfo) getTag();
+ int[] size = getPreviewSize();
+ int cellWidth = LauncherAppState.getInstance()
+ .getDynamicGrid().getDeviceProfile().cellWidthPx;
+
+ return Math.min(size[0], info.spanX * cellWidth);
+ }
+
+ /**
+ * Helper method to get the string info of the tag.
+ */
+ private String getTagToString() {
+ if (getTag() instanceof PendingAddWidgetInfo) {
+ return ((PendingAddWidgetInfo)getTag()).toString();
+ } else if (getTag() instanceof PendingAddShortcutInfo) {
+ return ((PendingAddShortcutInfo)getTag()).toString();
+ }
+ return "";
+ }
}
diff --git a/src/com/android/launcher3/PagedViewWidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index 7d8279547..75167bc7d 100644
--- a/src/com/android/launcher3/PagedViewWidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;
-public class PagedViewWidgetImageView extends ImageView {
+public class WidgetImageView extends ImageView {
public boolean mAllowRequestLayout = true;
- public PagedViewWidgetImageView(Context context, AttributeSet attrs) {
+ public WidgetImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -44,6 +44,5 @@ public class PagedViewWidgetImageView extends ImageView {
super.onDraw(canvas);
canvas.restore();
-
}
}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
new file mode 100644
index 000000000..292a5de20
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2015 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.launcher3.widget;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DragController;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.Folder;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.Workspace;
+
+import java.util.ArrayList;
+
+/**
+ * The widgets list view container.
+ */
+public class WidgetsContainerView extends FrameLayout implements Insettable, View.OnTouchListener,
+ View.OnLongClickListener, DragSource{
+
+ private static final String TAG = "WidgetContainerView";
+ private static final boolean DEBUG = false;
+
+ /* {@link RecyclerView} will keep following # of views in cache, before recycling. */
+ private static final int WIDGET_CACHE_SIZE = 2;
+
+ /* Global instances that are used inside this container. */
+ private Launcher mLauncher;
+ private DragController mDragController;
+ private IconCache mIconCache;
+
+ /* Data model for the widget */
+ private WidgetsModel mWidgets;
+
+ /* Recycler view related member variables */
+ private RecyclerView mView;
+ private WidgetsListAdapter mAdapter;
+
+ /* Dragging related. */
+ private boolean mDraggingWidget = false; // TODO(hyunyoungs): seems not needed? check!
+ private Point mLastTouchDownPos = new Point();
+
+ /* Rendering related. */
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+ private Rect mPadding = new Rect();
+
+ public WidgetsContainerView(Context context) {
+ this(context, null);
+ }
+
+ public WidgetsContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = (Launcher) context;
+ mDragController = mLauncher.getDragController();
+
+ mAdapter = new WidgetsListAdapter(context, this, mLauncher, this, mLauncher);
+ mWidgets = new WidgetsModel(context, mAdapter);
+ mAdapter.setWidgetsModel(mWidgets);
+ mIconCache = (LauncherAppState.getInstance()).getIconCache();
+
+ if (DEBUG) {
+ Log.d(TAG, "WidgetsContainerView constructor");
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ if (DEBUG) {
+ Log.d(TAG, String.format("onFinishInflate [widgets size=%d]",
+ mWidgets.getPackageSize()));
+ }
+ mView = (RecyclerView) findViewById(R.id.widgets_list_view);
+ mView.setAdapter(mAdapter);
+ mView.setLayoutManager(new LinearLayoutManager(getContext()));
+ mView.setItemViewCacheSize(WIDGET_CACHE_SIZE);
+
+ mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+ getPaddingBottom());
+ }
+
+ //
+ // Returns views used for launcher transitions.
+ //
+
+ public View getContentView() {
+ return findViewById(R.id.widgets_content);
+ }
+
+ public View getRevealView() {
+ // TODO(hyunyoungs): temporarily use apps view transition.
+ return findViewById(R.id.widgets_reveal_view);
+ }
+
+ public void scrollToTop() {
+ mView.scrollToPosition(0);
+ if (DEBUG) {
+ Log.d(TAG, String.format("scrollToTop, [widgets size=%d]",
+ mWidgets.getPackageSize()));
+ }
+ }
+
+ //
+ // Touch related handling.
+ //
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("onLonglick [v=%s]", v));
+ }
+
+ // Return early if this is not initiated from a touch
+ if (!v.isInTouchMode()) return false;
+ // When we have exited all apps or are in transition, disregard long clicks
+ if (!mLauncher.isWidgetsViewVisible() ||
+ mLauncher.getWorkspace().isSwitchingState()) return false;
+ // Return if global dragging is not enabled
+ Log.d(TAG, String.format("onLonglick dragging enabled?.", v));
+ if (!mLauncher.isDraggingEnabled()) return false;
+
+ return beginDragging(v);
+ }
+
+ private boolean beginDragging(View v) {
+ if (v instanceof WidgetCell) {
+ if (!beginDraggingWidget((WidgetCell) v)) {
+ return false;
+ }
+ } else {
+ Log.e(TAG, "Unexpected dragging view: " + v);
+ }
+
+ // 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()) {
+ // Go into spring loaded mode (must happen before we startDrag())
+ mLauncher.enterSpringLoadedDragMode();
+ }
+ }
+ }, 150);
+
+ return true;
+ }
+
+ private boolean beginDraggingWidget(WidgetCell 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;
+ Point previewPadding = null;
+
+ 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.
+
+ PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
+ int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
+
+ FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
+ float minScale = 1.25f;
+ int maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
+
+ int[] previewSizeBeforeScale = new int[1];
+ preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
+ maxWidth, null, previewSizeBeforeScale);
+ // Compare the size of the drag preview to the preview in the AppsCustomize tray
+ int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
+ v.getActualItemWidth());
+ scale = previewWidthInAppsCustomize / (float) preview.getWidth();
+
+ // The bitmap in the AppsCustomize tray is always the the same size, so there
+ // might be extra pixels around the preview itself - this accounts for that
+ if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
+ int padding =
+ (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
+ previewPadding = new Point(padding, 0);
+ }
+ } else {
+ PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
+ Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
+ preview = Utilities.createIconBitmap(icon, mLauncher);
+ createItemInfo.spanX = createItemInfo.spanY = 1;
+ }
+
+ // Don't clip alpha values for the drag outline if we're using the default widget preview
+ boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
+ (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
+
+ // Save the preview for the outline generation, then dim the preview
+ outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
+ false);
+
+ // Start the drag
+ mLauncher.lockScreenOrientation();
+ mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
+ mDragController.startDrag(image, preview, this, createItemInfo,
+ DragController.DRAG_ACTION_COPY, previewPadding, scale);
+ outline.recycle();
+ preview.recycle();
+ return true;
+ }
+
+ /*
+ * @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
+ */
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ Log.d(TAG, String.format("onTouch [MotionEvent=%s]", ev));
+ if (ev.getAction() == MotionEvent.ACTION_DOWN ||
+ ev.getAction() == MotionEvent.ACTION_MOVE) {
+ mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
+ }
+ return false;
+ }
+
+ //
+ // Drag related handling methods that implement {@link DragSource} interface.
+ //
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsAppInfoDropTarget() {
+ return true;
+ }
+
+ /*
+ * Both this method and {@link #supportsFlingToDelete} has to return {@code false} for the
+ * {@link DeleteDropTarget} to be invisible.)
+ */
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return false;
+ }
+
+ @Override
+ public float getIntrinsicIconScaleFactor() {
+ return 0;
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // We just dismiss the drag when we fling, so cleanup here
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ mLauncher.unlockScreenOrientation(false);
+ }
+
+ @Override
+ public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+ boolean success) {
+ if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+ !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+ // Exit spring loaded mode if we have not successfully dropped or have not handled the
+ // drop in Workspace
+ mLauncher.exitSpringLoadedDragModeDelayed(true,
+ Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+ }
+ mLauncher.unlockScreenOrientation(false);
+
+ // 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;
+ }
+ }
+
+ //
+ // Container rendering related.
+ //
+
+ /*
+ * @see Insettable#setInsets(Rect)
+ */
+ @Override
+ public void setInsets(Rect insets) {
+ setPadding(mPadding.left + insets.left, mPadding.top + insets.top,
+ mPadding.right + insets.right, mPadding.bottom + insets.bottom);
+ }
+
+ /**
+ * Initialize the widget data model.
+ */
+ public void addWidgets(ArrayList<Object> widgetsShortcuts, PackageManager pm) {
+ mWidgets.addWidgetsAndShortcuts(widgetsShortcuts, pm);
+ }
+
+ private WidgetPreviewLoader getWidgetPreviewLoader() {
+ if (mWidgetPreviewLoader == null) {
+ mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
+ }
+ return mWidgetPreviewLoader;
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
new file mode 100644
index 000000000..afeb2d385
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 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.launcher3.widget;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.List;
+
+/**
+ * List view adapter for the widget tray.
+ *
+ * <p>Memory vs. Performance:
+ * The less number of types of views are inserted into a {@link RecyclerView}, the more recycling
+ * happens and less memory is consumed. {@link #getItemViewType} was not overridden as there is
+ * only a single type of view.
+ */
+public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> {
+
+ private static final String TAG = "WidgetsListAdapter";
+ private static final boolean DEBUG = false;
+
+ private Context mContext;
+ private Launcher mLauncher;
+ private LayoutInflater mLayoutInflater;
+ private IconCache mIconCache;
+
+ private WidgetsModel mWidgetsModel;
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+
+ private View.OnTouchListener mTouchListener;
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+
+
+ public WidgetsListAdapter(Context context,
+ View.OnTouchListener touchListener,
+ View.OnClickListener iconClickListener,
+ View.OnLongClickListener iconLongClickListener,
+ Launcher launcher) {
+ mLayoutInflater = LayoutInflater.from(context);
+ mContext = context;
+
+ mTouchListener = touchListener;
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+
+ mLauncher = launcher;
+ mIconCache = LauncherAppState.getInstance().getIconCache();
+ }
+
+ public void setWidgetsModel(WidgetsModel w) {
+ mWidgetsModel = w;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mWidgetsModel.getPackageSize();
+ }
+
+ @Override
+ public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
+ List<Object> infoList = mWidgetsModel.getSortedWidgets(pos);
+
+ ViewGroup row = ((ViewGroup) holder.getContent().findViewById(R.id.widgets_cell_list));
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "onBindViewHolder [pos=%d, widget#=%d, row.getChildCount=%d]",
+ pos, infoList.size(), row.getChildCount()));
+ }
+
+ // Add more views.
+ // if there are too many, hide them.
+ int diff = infoList.size() - row.getChildCount();
+ if (diff > 0) {
+ for (int i = 0; i < diff; i++) {
+ WidgetCell widget = new WidgetCell(mContext);
+ widget = (WidgetCell) mLayoutInflater.inflate(
+ R.layout.widget_cell, row, false);
+
+ // set up touch.
+ widget.setOnClickListener(mIconClickListener);
+ widget.setOnLongClickListener(mIconLongClickListener);
+ widget.setOnTouchListener(mTouchListener);
+ row.addView(widget);
+ }
+ } else if (diff < 0) {
+ for (int i=infoList.size() ; i < row.getChildCount(); i++) {
+ row.getChildAt(i).setVisibility(View.GONE);
+ }
+ }
+
+ // Bind the views in the application info section.
+ PackageItemInfo infoOut = mWidgetsModel.getPackageItemInfo(pos);
+ if (infoOut.usingLowResIcon) {
+ // TODO(hyunyoungs): call this in none UI thread in the same way as BubbleTextView.
+ mIconCache.getTitleAndIconForApp(infoOut.packageName,
+ UserHandleCompat.myUserHandle(), false /* useLowResIcon */, infoOut);
+ }
+ ((TextView) holder.getContent().findViewById(R.id.section)).setText(infoOut.title);
+ ImageView iv = (ImageView) holder.getContent().findViewById(R.id.section_image);
+ iv.setImageBitmap(infoOut.iconBitmap);
+
+ // Bind the view in the widget horizontal tray region.
+ for (int i=0; i < infoList.size(); i++) {
+ WidgetCell widget = (WidgetCell) row.getChildAt(i);
+ widget.reset();
+ if (getWidgetPreviewLoader() == null) {
+ return;
+ }
+ if (infoList.get(i) instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) infoList.get(i);
+ PendingAddWidgetInfo pawi = new PendingAddWidgetInfo(info, null);
+ widget.setTag(pawi);
+ widget.applyFromAppWidgetProviderInfo(info, -1, mWidgetPreviewLoader);
+ } else if (infoList.get(i) instanceof ResolveInfo) {
+ ResolveInfo info = (ResolveInfo) infoList.get(i);
+ PendingAddShortcutInfo pasi = new PendingAddShortcutInfo(info.activityInfo);
+ widget.setTag(pasi);
+ widget.applyFromResolveInfo(mLauncher.getPackageManager(), info, mWidgetPreviewLoader);
+ }
+ widget.setVisibility(View.VISIBLE);
+ widget.ensurePreview();
+ }
+ }
+
+ @Override
+ public WidgetsRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("\nonCreateViewHolder, [widget#=%d]", viewType));
+ }
+
+ ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
+ R.layout.widgets_list_row_view, parent, false);
+ return new WidgetsRowViewHolder(container);
+ }
+
+ @Override
+ public long getItemId(int pos) {
+ return pos;
+ }
+
+ private WidgetPreviewLoader getWidgetPreviewLoader() {
+ if (mWidgetPreviewLoader == null) {
+ mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
+ }
+ return mWidgetPreviewLoader;
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsModel.java b/src/com/android/launcher3/widget/WidgetsModel.java
new file mode 100644
index 000000000..71a7b9446
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsModel.java
@@ -0,0 +1,128 @@
+
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel.WidgetAndShortcutNameComparator;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Widgets data model that is used by the adapters of the widget views and controllers.
+ *
+ * <p> The widgets and shortcuts are organized using package name as its index.
+ */
+public class WidgetsModel {
+
+ private static final String TAG = "WidgetsModel";
+ private static final boolean DEBUG = false;
+
+ /* List of packages that is tracked by this model. */
+ private List<PackageItemInfo> mPackageItemInfos = new ArrayList<>();
+
+ /* Map of widgets and shortcuts that are tracked per package. */
+ private Map<PackageItemInfo, ArrayList<Object>> mWidgetsList = new HashMap<>();
+
+ /* Notifies the adapter when data changes. */
+ private RecyclerView.Adapter mAdapter;
+
+ private Comparator mWidgetAndShortcutNameComparator;
+
+ private IconCache mIconCache;
+
+ public WidgetsModel(Context context, RecyclerView.Adapter adapter) {
+ mAdapter = adapter;
+ mWidgetAndShortcutNameComparator = new WidgetAndShortcutNameComparator(context);
+ mIconCache = LauncherAppState.getInstance().getIconCache();
+ }
+
+ // Access methods that may be deleted if the private fields are made package-private.
+ public int getPackageSize() {
+ return mPackageItemInfos.size();
+ }
+
+ // Access methods that may be deleted if the private fields are made package-private.
+ public PackageItemInfo getPackageItemInfo(int pos) {
+ return mPackageItemInfos.get(pos);
+ }
+
+ public List<Object> getSortedWidgets(int pos) {
+ return mWidgetsList.get(mPackageItemInfos.get(pos));
+ }
+
+ public void addWidgetsAndShortcuts(ArrayList<Object> widgetsShortcuts, PackageManager pm) {
+ if (DEBUG) {
+ Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + widgetsShortcuts.size());
+ }
+
+ // Temporary list for {@link PackageItemInfos} to avoid having to go through
+ // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
+ HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
+
+ // clear the lists.
+ mWidgetsList.clear();
+ mPackageItemInfos.clear();
+
+ // add and update.
+ for (Object o: widgetsShortcuts) {
+ String packageName = "";
+ if (o instanceof LauncherAppWidgetProviderInfo) {
+ LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+ packageName = widgetInfo.provider.getPackageName();
+ } else if (o instanceof ResolveInfo) {
+ ResolveInfo resolveInfo = (ResolveInfo) o;
+ packageName = resolveInfo.activityInfo.packageName;
+ } else {
+ Log.e(TAG, String.format("addWidgetsAndShortcuts, nothing added for class=%s",
+ o.getClass().toString()));
+ }
+
+ PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
+ ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(pInfo);
+ if (widgetsShortcutsList != null) {
+ widgetsShortcutsList.add(o);
+ } else {
+ widgetsShortcutsList = new ArrayList<Object>();
+ widgetsShortcutsList.add(o);
+
+ pInfo = new PackageItemInfo(packageName);
+ mIconCache.getTitleAndIconForApp(packageName, UserHandleCompat.myUserHandle(),
+ true /* useLowResIcon */, pInfo);
+ mWidgetsList.put(pInfo, widgetsShortcutsList);
+ tmpPackageItemInfos.put(packageName, pInfo);
+ mPackageItemInfos.add(pInfo);
+ }
+ }
+
+ // sort.
+ sortPackageItemInfos();
+ for (PackageItemInfo p: mPackageItemInfos) {
+ Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator);
+ }
+
+ // notify.
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void sortPackageItemInfos() {
+ Collections.sort(mPackageItemInfos, new Comparator<PackageItemInfo>() {
+ @Override
+ public int compare(PackageItemInfo lhs, PackageItemInfo rhs) {
+ return lhs.title.toString().compareTo(rhs.title.toString());
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/PagedViewGridLayout.java b/src/com/android/launcher3/widget/WidgetsRowView.java
index f69fa562d..54667384b 100644
--- a/src/com/android/launcher3/PagedViewGridLayout.java
+++ b/src/com/android/launcher3/widget/WidgetsRowView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,36 +14,31 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.widget;
import android.content.Context;
import android.view.MotionEvent;
-import android.view.View;
import android.widget.FrameLayout;
-import android.widget.GridLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
/**
- * The grid based layout used strictly for the widget/wallpaper tab of the AppsCustomize pane
+ * Layout used for widget tray rows for each app. For performance, this view can be replaced with
+ * a {@link RecyclerView} in the future if we settle on scrollable single row for the widgets.
+ * If we decide on collapsable grid, then HorizontalScrollView can be replaced with a
+ * {@link GridLayout}.
*/
-public class PagedViewGridLayout extends GridLayout implements Page {
- static final String TAG = "PagedViewGridLayout";
+public class WidgetsRowView extends HorizontalScrollView {
+ static final String TAG = "WidgetsRow";
- private int mCellCountX;
- private int mCellCountY;
private Runnable mOnLayoutListener;
+ private String mAppName;
- public PagedViewGridLayout(Context context, int cellCountX, int cellCountY) {
+ public WidgetsRowView(Context context, String appName) {
super(context, null, 0);
- mCellCountX = cellCountX;
- mCellCountY = cellCountY;
- }
-
- int getCellCountX() {
- return mCellCountX;
- }
-
- int getCellCountY() {
- return mCellCountY;
+ mAppName = appName;
}
/**
@@ -57,6 +52,13 @@ public class PagedViewGridLayout extends GridLayout implements Page {
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ TextView tv = (TextView) findViewById(R.id.widget_name);
+ tv.setText(mAppName);
+ }
+
+ @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mOnLayoutListener = null;
@@ -66,6 +68,7 @@ public class PagedViewGridLayout extends GridLayout implements Page {
mOnLayoutListener = r;
}
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mOnLayoutListener != null) {
@@ -76,43 +79,9 @@ public class PagedViewGridLayout extends GridLayout implements Page {
@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;
}
- @Override
- public void removeAllViewsOnPage() {
- removeAllViews();
- mOnLayoutListener = null;
- setLayerType(LAYER_TYPE_NONE, null);
- }
-
- @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/launcher3/UserInitializeReceiver.java b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
index d8e17b12f..99a192c89 100644
--- a/src/com/android/launcher3/UserInitializeReceiver.java
+++ b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -13,20 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.launcher3.widget;
-package com.android.launcher3;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
+public class WidgetsRowViewHolder extends ViewHolder {
-/**
- * Takes care of setting initial wallpaper for a user, by selecting the
- * first wallpaper that is not in use by another user.
- */
-public class UserInitializeReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO: initial wallpaper now that wallpapers are owned by another app
+ ViewGroup mContent;
+
+ public WidgetsRowViewHolder(ViewGroup v) {
+ super(v);
+ mContent = v;
+ }
+
+ ViewGroup getContent() {
+ return mContent;
}
}
diff --git a/tests/Android.mk b/tests/Android.mk
index 762a52b8d..eba4ade48 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 The Android Open Source Project
+# Copyright (C) 2015 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.
@@ -12,5 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-#LOCAL_PATH := $(call my-dir)
-#include $(call all-makefiles-under,$(LOCAL_PATH))
+
+LOCAL_PATH := $(call my-dir)
+
+src_dirs := src
+res_dirs := res
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+
+LOCAL_SDK_VERSION := 21
+
+LOCAL_PACKAGE_NAME := Launcher3Tests
+
+LOCAL_INSTRUMENTATION_FOR := Launcher3
+
+include $(BUILD_PACKAGE)
diff --git a/tests/stress/AndroidManifest.xml b/tests/AndroidManifest.xml
index bcca1ff53..42ae5a38a 100644
--- a/tests/stress/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="2.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- Copyright (C) 2015 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.
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.launcher3.stress.launcherrotation">
+ package="com.android.launcher3.tests">
<application>
<uses-library android:name="android.test.runner" />
@@ -24,6 +24,6 @@
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.launcher3"
- android:label="Rotation stress test using Launcher2">
+ android:label="Unit tests for Launcher3">
</instrumentation>
</manifest>
diff --git a/tests/res/values/string.xml b/tests/res/values/string.xml
new file mode 100644
index 000000000..3c1ec5c61
--- /dev/null
+++ b/tests/res/values/string.xml
@@ -0,0 +1,21 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Dummy string for tests. [DO NOT TRANSLATE] -->
+ <string name="dummy" >Dummy string for tests.</string>
+
+</resources>
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
new file mode 100644
index 000000000..ea87014e9
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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.launcher3;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import com.android.launcher3.util.FocusLogic;
+
+/**
+ * Tests the {@link FocusLogic} class that handles key event based focus handling.
+ */
+@SmallTest
+public final class FocusLogicTest extends AndroidTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Nothing to set up as this class only tests static methods.
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Nothing to tear down as this class only tests static methods.
+ }
+
+ public void testShouldConsume() {
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
+ }
+
+ public void testCreateSparseMatrix() {
+ // Either, 1) create a helper method to generate/instantiate all possible cell layout that
+ // may get created in real world to test this method. OR 2) Move all the matrix
+ // management routine to celllayout and write tests for them.
+ }
+}
diff --git a/tests/stress/Android.mk b/tests/stress/Android.mk
deleted file mode 100644
index 68289bd3e..000000000
--- a/tests/stress/Android.mk
+++ /dev/null
@@ -1,31 +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.
-#
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := LauncherRotationStressTest
-
-LOCAL_CERTIFICATE := shared
-
-LOCAL_INSTRUMENTATION_FOR := Launcher2
-
-include $(BUILD_PACKAGE)
diff --git a/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java b/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java
deleted file mode 100644
index a5b85eb19..000000000
--- a/tests/stress/src/com/android/launcher3/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.launcher3.stress;
-
-
-import com.android.launcher3.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<Launcher> {
-
- 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);
- }
-}