Add logging for workspace rearrangement in overview mode
am: 0d4899026c
Change-Id: Ie6569ecafdd63981e167a910f852a8133697ae81
diff --git a/Android.mk b/Android.mk
index 202ff44..28beff2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -35,7 +35,7 @@
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res \
- frameworks/support/v7/recyclerview/res
+ prebuilts/sdk/current/support/v7/recyclerview/res \
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/build.gradle b/build.gradle
index e103d79..4629caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.1.3'
+ classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
}
}
@@ -45,6 +45,7 @@
androidTest {
java.srcDirs = ['tests/src']
+ res.srcDirs = ['tests/res']
manifest.srcFile "tests/AndroidManifest.xml"
}
@@ -65,6 +66,9 @@
compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
testCompile 'junit:junit:4.12'
+ androidTestCompile "org.mockito:mockito-core:1.+"
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
androidTestCompile 'com.android.support:support-annotations:23.2.0'
@@ -80,7 +84,7 @@
task.builtins {
remove java
javanano {
- option 'ignore_services=false'
+ option "java_package=launcher_log.proto|com.android.launcher3.userevent.nano"
}
}
}
diff --git a/proguard.flags b/proguard.flags
index ad6f727..f1a3eaf 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -86,7 +86,8 @@
# next row when focus is on the last item of last row when using a RecyclerView
# Keep optimized and shrunk proguard to prevent issues like this when using
# support jar.
--keep,allowoptimization,allowshrinking class android.support.** {
- *;
-}
+#-keep,allowoptimization,allowshrinking class android.support.** {
+# *;
+#}
+-keep class android.support.v7.widget.RecyclerView { *; }
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 448cf64..6b27559 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -15,7 +15,7 @@
*/
syntax = "proto2";
-option java_package = "com.android.launcher3.userevent.nano";
+option java_package = "com.android.launcher3.userevent";
option java_outer_classname = "LauncherLogProto";
package userevent;
diff --git a/res/drawable-hdpi/ic_all_apps_bg_hand.png b/res/drawable-hdpi/ic_all_apps_bg_hand.png
index dff2f54..437fd37 100644
--- a/res/drawable-hdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-hdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_bitmap.9.png b/res/drawable-hdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index d2aee73..0000000
--- a/res/drawable-hdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 78345b8..0000000
--- a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_hand.png b/res/drawable-mdpi/ic_all_apps_bg_hand.png
index 0d1d7bb..0a00241 100644
--- a/res/drawable-mdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-mdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_bitmap.9.png b/res/drawable-mdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 9325d49..0000000
--- a/res/drawable-mdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index bf74fa0..0000000
--- a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-v21/quantum_panel.xml b/res/drawable-v21/quantum_panel.xml
deleted file mode 100644
index d1c0783..0000000
--- a/res/drawable-v21/quantum_panel.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/quantum_panel_shape"
- android:insetBottom="@dimen/quantum_panel_outer_padding"
- android:insetLeft="@dimen/quantum_panel_outer_padding"
- android:insetRight="@dimen/quantum_panel_outer_padding"
- android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/drawable-v21/quantum_panel_dark.xml b/res/drawable-v21/quantum_panel_dark.xml
deleted file mode 100644
index 405ad51..0000000
--- a/res/drawable-v21/quantum_panel_dark.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/quantum_panel_shape_dark"
- android:insetBottom="@dimen/quantum_panel_outer_padding"
- android:insetLeft="@dimen/quantum_panel_outer_padding"
- android:insetRight="@dimen/quantum_panel_outer_padding"
- android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_hand.png b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
index e727d37..1acb378 100644
--- a/res/drawable-xhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index b89e8b4..0000000
--- a/res/drawable-xhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 1d17136..0000000
--- a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
index fffcc6b..09c6c8d 100644
--- a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 1dd1f6d..0000000
--- a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 48d584b..0000000
--- a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
index 4d065d8..49c004d 100644
--- a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 915177d..0000000
--- a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 27b8466..0000000
--- a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/quantum_panel.xml b/res/drawable/quantum_panel.xml
index 1f4fb71..fda1003 100644
--- a/res/drawable/quantum_panel.xml
+++ b/res/drawable/quantum_panel.xml
@@ -14,5 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/quantum_panel_bitmap" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/quantum_panel_shape"
+ android:insetBottom="@dimen/quantum_panel_outer_padding"
+ android:insetLeft="@dimen/quantum_panel_outer_padding"
+ android:insetRight="@dimen/quantum_panel_outer_padding"
+ android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/drawable/quantum_panel_dark.xml b/res/drawable/quantum_panel_dark.xml
index 6642e78..b113b37 100644
--- a/res/drawable/quantum_panel_dark.xml
+++ b/res/drawable/quantum_panel_dark.xml
@@ -14,5 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/quantum_panel_dark_bitmap" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/quantum_panel_shape_dark"
+ android:insetBottom="@dimen/quantum_panel_outer_padding"
+ android:insetLeft="@dimen/quantum_panel_outer_padding"
+ android:insetRight="@dimen/quantum_panel_outer_padding"
+ android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index a2e2f9b..dd981dd 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -77,8 +77,7 @@
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="invisible"
- launcher:layout_ignoreInsets="true" />
+ android:visibility="invisible" />
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 12c01b7..06cb550 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -76,8 +76,7 @@
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="invisible"
- launcher:layout_ignoreInsets="true" />
+ android:visibility="invisible" />
</com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 4909eb3..1909f3b 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -46,21 +46,30 @@
<!-- DO NOT CHANGE THE ID -->
<com.android.launcher3.allapps.AllAppsRecyclerView
android:id="@+id/apps_list_view"
+ android:layout_below="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|top"
- android:layout_marginTop="@dimen/all_apps_search_bar_height"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:focusable="true"
+ android:paddingStart="@dimen/container_fastscroll_thumb_max_width"
+ android:paddingEnd="@dimen/container_fastscroll_thumb_max_width"
android:theme="@style/CustomOverscroll.Light" />
+ <!-- Fast scroller popup -->
+ <TextView
+ style="@style/FastScrollerPopup"
+ android:layout_below="@+id/search_container"
+ android:id="@+id/fast_scroller_popup"
+ android:layout_alignParentEnd="true"
+ android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
+
<FrameLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_height"
android:layout_gravity="center|top"
- android:paddingTop="@dimen/all_apps_search_bar_margin_top"
android:gravity="center|bottom"
android:orientation="horizontal"
android:saveEnabled="false">
@@ -68,8 +77,9 @@
<com.android.launcher3.ExtendedEditText
android:id="@+id/search_box_input"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
android:background="@android:color/transparent"
+ android:layout_gravity="bottom"
android:focusableInTouchMode="true"
android:gravity="center"
android:imeOptions="actionSearch|flagNoExtractUi"
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
new file mode 100644
index 0000000..91a1e45
--- /dev/null
+++ b/res/layout/app_widget_resize_frame.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.AppWidgetResizeFrame
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/widget_resize_shadow"
+ android:foreground="@drawable/widget_resize_frame"
+ android:padding="0dp" >
+
+ <!-- Left -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="left|center_vertical"
+ android:layout_marginLeft="@dimen/widget_handle_margin" />
+
+ <!-- Top -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="top|center_horizontal"
+ android:layout_marginTop="@dimen/widget_handle_margin" />
+
+ <!-- Right -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="right|center_vertical"
+ android:layout_marginRight="@dimen/widget_handle_margin" />
+
+ <!-- Bottom -->
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_widget_resize_handle"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/widget_handle_margin" />
+
+</com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
index 708ece4..d6bd0c5 100644
--- a/res/layout/appwidget_error.xml
+++ b/res/layout/appwidget_error.xml
@@ -19,6 +19,7 @@
android:layout_height="match_parent"
android:gravity="center"
android:elevation="2dp"
+ android:theme="@style/WidgetContainerTheme"
android:background="@drawable/quantum_panel_dark"
android:textAppearance="?android:attr/textAppearanceMediumInverse"
android:textColor="@color/widgets_view_item_text_color"
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 9ba3f09..2091721 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -15,11 +15,13 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ launcher:layout_ignoreInsets="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:gravity="top"
- android:orientation="horizontal" >
+ android:orientation="horizontal">
<TextView
android:id="@+id/wallpaper_button"
diff --git a/res/layout/qsb_blocker_view.xml b/res/layout/qsb_blocker_view.xml
index 58a148e..453eebe 100644
--- a/res/layout/qsb_blocker_view.xml
+++ b/res/layout/qsb_blocker_view.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.QsbBlockerView
+<com.android.launcher3.qsb.QsbBlockerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/res/layout/qsb_container.xml b/res/layout/qsb_container.xml
index b75e3b5..6fa843d 100644
--- a/res/layout/qsb_container.xml
+++ b/res/layout/qsb_container.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.QsbContainerView
+<com.android.launcher3.qsb.QsbContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -23,8 +23,8 @@
android:padding="0dp" >
<fragment
- android:name="com.android.launcher3.QsbContainerView$QsbFragment"
+ android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
android:layout_width="match_parent"
android:tag="qsb_view"
android:layout_height="match_parent"/>
-</com.android.launcher3.QsbContainerView>
\ No newline at end of file
+</com.android.launcher3.qsb.QsbContainerView>
\ No newline at end of file
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index c4431be..d193a5e 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -49,6 +49,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <!-- Fast scroller popup -->
+ <TextView
+ style="@style/FastScrollerPopup"
+ android:layout_below="@+id/search_container"
+ android:id="@+id/fast_scroller_popup"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
+
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 9152ea5..dd427e3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Afgelaaide program in veiligmodus gedeaktiveer"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
+ <string name="home_screen" msgid="806512411299847073">"Tuisskerm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Gepasmaakte handelinge"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Raak en hou om \'n legstuk op te tel."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dubbeltik en hou om \'n legstuk op te tel of gebruik gepasmaakte handelinge."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index c20dc12..02d5cc1 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"የወረደው መተግበሪያ ደህንነቱ በተጠበቀ ሁኔታ ውስጥ ተሰናክሏል"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
+ <string name="home_screen" msgid="806512411299847073">"መነሻ ገጽ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ብጁ እርምጃዎች"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ፍርግም ለማንሳት ይንኩ እና ይያዙት"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"አንድ ንዑስ ፕሮግራም ለመምረጥ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ አድርገው ይያዙ።"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index c80d162..437e974 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"تم تعطيل التطبيق الذي تم تنزيله في الوضع الآمن"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات معطلة في الوضع الآمن"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
+ <string name="home_screen" msgid="806512411299847073">"الشاشة الرئيسية"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"الإجراءات المخصّصة"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"المس مع الاستمرار لاختيار إحدى الأدوات."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"انقر نقرًا مزدوجًا مع الاستمرار لاختيار أداة أو استخدم الإجراءات المخصصة."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 1a4f31b..fe0ef4f 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Güvənli rejimdə icazə verilməyən tətbiq endirildi"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
+ <string name="home_screen" msgid="806512411299847073">"Əsas ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Fərdi əməliyyatlar"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidceti götürmək üçün toxunub saxlayın."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Vidceti götürmək üçün & iki dəfə toxunub saxlayın və ya fərdi fəaliyyətləri istifadə edin."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index a9344f9..bd7b874 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija je onemogućena u Bezbednom režimu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
+ <string name="home_screen" msgid="806512411299847073">"Početni ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Prilagođene radnje"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i zadržite da biste izabrali vidžet."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite i zadržite da biste izabrali vidžet ili koristite prilagođene radnje."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
index f5dba2d..b15dc60 100644
--- a/res/values-be-rBY/strings.xml
+++ b/res/values-be-rBY/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Спампаваная праграма адключана ў Бяспечным рэжыме"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Віджэты адключаны ў Бяспечным рэжыме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недаступны"</string>
+ <string name="home_screen" msgid="806512411299847073">"Галоўны экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Спецыяльныя дзеянні"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Дакраніцеся і ўтрымлiвайце віджэт, каб выбр. яго."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Дакраніцеся двойчы і ўтрымлівайце, каб выбраць віджэт або выкарыстоўваць карыстальніцкія дзеянні."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 2c6d3d4..13d1641 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Изтегленото приложение е деактивирано в безопасния режим"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
+ <string name="home_screen" msgid="806512411299847073">"Начален екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Персонализирани действия"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Докоснете и задръжте за избор на приспособление."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Докоснете двукратно и задръжте за избор на приспособление или използвайте персонализирани действия."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index d5108e9..60cdd47 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনলোড করা অ্যাপ্লিকেশান নিরাপদ মোডে অক্ষম রয়েছে"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
+ <string name="home_screen" msgid="806512411299847073">"হোম স্ক্রীন"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"কাস্টম অ্যাকশন"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"একটি উইজেট তুলতে তা স্পর্শ করে ধরে রাখুন৷"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"কোনো উইজেট বেছে নিতে দুবার-আলতো চেপে ধরে থাকুন অথবা কাস্টম ক্রিয়াগুলি ব্যবহার করুন৷"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 579b95a..705ca04 100644
--- a/res/values-bs-rBA/strings.xml
+++ b/res/values-bs-rBA/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija je onemogućena u sigurnom načinu rada"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
+ <string name="home_screen" msgid="806512411299847073">"Početni ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Prilagođene akcije"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite & i držite da biste uzeli dodatak."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite & i držite da biste uzeli vidžet ili koristite prilagođene radnje."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 4ba0498..c12ec8d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'aplicació que has baixat està desactivada al mode segur."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla d\'inici"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Accions personalitzades"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premut un widget per triar-lo."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Fes doble toc i mantén premut per seleccionar un widget o per utilitzar les accions personalitzades."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 373920c..8eacd0a 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Stažená aplikace je v nouzovém režimu zakázána"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
+ <string name="home_screen" msgid="806512411299847073">"Plocha"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Vlastní akce"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget vyberete dotykem a podržením."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvojitým klepnutím a podržením vyberte widget, případně použijte vlastní akce."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 9304140..220bd4b 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloadet app er deaktiveret i sikker tilstand"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startskærm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Brugerdefinerede handlinger"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryk på en widget, og hold den nede for at vælge."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tryk to gange, og hold fingeren nede for at vælge en widget eller bruge tilpassede handlinger."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e461d46..990c901 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Heruntergeladene App im abgesicherten Modus deaktiviert"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startbildschirm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Benutzerdefinierte Aktionen"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Zum Hinzufügen Widget berühren und halten"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Zum Hinzufügen auf Widget doppeltippen und gedrückt halten oder benutzerdefinierte Aktionen verwenden."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 119232d..c82b7ee 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Η λήψη εφαρμογών απενεργοποήθηκε στην Ασφαλή λειτουργία"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
+ <string name="home_screen" msgid="806512411299847073">"Αρχική οθόνη"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Προσαρμοσμένες ενέργειες"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Αγγίξτε παρατεταμένα για να πάρετε ένα γραφ.στοιχ."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Πατήστε δύο φορές παρατεταμένα για επιλογή γραφικού στοιχείου ή χρήση προσαρμοσμένων ενεργειών."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 44daaa8..baae4b0 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 44daaa8..baae4b0 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 44daaa8..baae4b0 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4bd6c88..250f784 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla principal"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Acciones personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén presionado el widget que desees elegir."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Presiona dos veces y mantén presionado para elegir un widget o usa una acción personalizada."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a4fc620..1fdf3ef 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla de inicio"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Acciones personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén pulsado el widget que quieras seleccionar."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toca dos veces y mantén pulsado el widget que quieras seleccionar o utiliza acciones personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index feb63ca..a0ee1e9 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Allalaetud rakendus on turvarežiimis keelatud"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
+ <string name="home_screen" msgid="806512411299847073">"Avaekraan"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Kohandatud toimingud"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidina valimiseks vajutage ja hoidke seda all."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Topeltpuudutage ja hoidke vidina valimiseks või kohandatud toimingute kasutamiseks."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 76ef5c6..b2aed2e 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Deskargatutako aplikazioa modu seguruan desgaitu da"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
+ <string name="home_screen" msgid="806512411299847073">"Hasierako pantaila"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ekintza pertsonalizatuak"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Eduki sakatuta widgeta aukeratzeko."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Sakatu birritan eta eduki sakatuta widgeta aukeratzeko edo ekintza pertsonalizatuak erabiltzeko."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 95675a7..88a4fb5 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"برنامه بارگیری شده در حالت ایمن غیرفعال شد"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ابزارکها در حالت ایمن غیرفعال هستند"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"میانبر دردسترس نیست"</string>
+ <string name="home_screen" msgid="806512411299847073">"صفحه اصلی"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"عملکردهای سفارشی"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"برای انتخاب ابزارک لمس کنید و نگه دارید."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"برای انتخاب یک ابزارک، دو ضربه سریع بزنید و نگهدارید یا از اقدامات سفارشی استفاده کنید."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 40e6d96..0aa01b9 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ladattu sovellus poistettiin käytöstä suojatussa tilassa"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
+ <string name="home_screen" msgid="806512411299847073">"Aloitusnäyttö"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Muokatut toiminnot"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Valitse widget painamalla sitä pitkään."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Valitse widget tai käytä muokattuja toimintoja kaksoisnapauttamalla ja painamalla kohdetta pitkään."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 01d6b27..e261b74 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Actions personnalisées"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Maintenez un doigt sur le widget pour l\'ajouter."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Touchez 2x un widget et maintenez doigt dessus pour l’ajouter ou utiliser des actions personnalisées"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f42748c..591dff7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Actions personnalisées"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"App. de manière prolongée pour sélectionner widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Appuyez 2 fois et maintenez la pression pour sélectionner widget ou utilisez actions personnalisées."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index c03ecdc..766cddb 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"A aplicación que descargaches está desactivada no modo seguro"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pantalla de inicio"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Accións personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premido un widget para seleccionalo."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toca dúas veces e mantén premido para seleccionar un widget ou utiliza accións personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 39a176c..c14816a 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"સુરક્ષિત મોડમાં ડાઉનલોડ કરેલ ઍપ્લિકેશન અક્ષમ કરી"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
+ <string name="home_screen" msgid="806512411299847073">"હોમ સ્ક્રીન"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"કસ્ટમ ક્રિયાઓ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"વિજેટ ચૂંટવા માટે ટચ કરો અને પકડી રાખો."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"વિજેટ ચૂંટવા અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરવા માટે બે વાર ટેપ કરો અને પકડી રાખો."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index f534e1e..3d1fecb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड किए गए ऐप्स सुरक्षित मोड में अक्षम है"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
+ <string name="home_screen" msgid="806512411299847073">"होम स्क्रीन"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"कस्टम कार्रवाई"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए स्पर्श करके रखें."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"कोई विजेट चुनने के लिए डबल टैप करके रखें या कस्टम कार्रवाइयां चुनें."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index b4fb50e..ce370f9 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija onemogućena je u Sigurnom načinu rada"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
+ <string name="home_screen" msgid="806512411299847073">"Početni zaslon"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Prilagođene radnje"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i držite kako biste podigli widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dodirnite dvaput i držite kako biste podigli widget ili pokušajte prilagođenim radnjama."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 279749b..c2003b6 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"A letöltött alkalmazás Csökkentett módban ki van kapcsolva"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
+ <string name="home_screen" msgid="806512411299847073">"Kezdőképernyő"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Egyéni műveletek"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Modul felvételéhez érintse meg, és tartsa lenyomva"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Modul mozgatásához koppintson rá duplán és tartsa lenyomva, vagy használjon egyéni műveleteket."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index c7401a9..7884895 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ներբեռնված ծրագիրն անջատված է Անվտանգ ռեժիմում"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
+ <string name="home_screen" msgid="806512411299847073">"Հիմնական էկրան"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Հատուկ գործողություններ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Հպեք և պահեք՝ վիջեթն ընտրելու համար:"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Կրկնակի հպեք և պահեք՝ վիջեթ ավելացնելու համար կամ օգտվեք հարմարեցրած գործողություններից:"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 00e8d63..16c3752 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikasi yang diunduh dinonaktifkan dalam mode Aman"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
+ <string name="home_screen" msgid="806512411299847073">"Layar utama"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tindakan khusus"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketuk dua kalip & tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 7586ae3..f39628d 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Sótt forrit er óvirkt í öryggisstillingu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
+ <string name="home_screen" msgid="806512411299847073">"Heimaskjár"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Sérsniðnar aðgerðir"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Haltu fingri á græju til að grípa hana."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ýttu tvisvar og haltu fingri á græju til að grípa hana eða notaðu sérsniðnar aðgerðir."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index b821222..e4e1242 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'app scaricata è stata disattivata in modalità provvisoria"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
+ <string name="home_screen" msgid="806512411299847073">"Schermata Home"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Azioni personalizzate"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Tocca e tieni premuto per scegliere un widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tocca due volte e tieni premuto per scegliere un widget o per utilizzare azioni personalizzate."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ffea51f..c04bf12 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"אפליקציה שהורדת הושבתה במצב בטוח"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
+ <string name="home_screen" msgid="806512411299847073">"מסך דף הבית"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"פעולות מותאמות אישית"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"גע נגיעה רציפה בווידג\'ט כדי לבחור בו."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"הקש פעמיים וגע נגיעה רציפה בווידג\'ט כדי לבחור בו, או השתמש בפעולות מותאמות אישית."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 8729aba..ea9d381 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ダウンロードしたアプリは、セーフモードでは無効です"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
+ <string name="home_screen" msgid="806512411299847073">"ホーム画面"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"カスタム操作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ウィジェットを追加するには押し続けます。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ダブルタップ後に押し続けてウィジェットを選択するか、カスタム操作を使用してください。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 1a200d7..7c41389 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"უსაფრთხო რეჟიმში ჩამოტვირთული აპი გაუქმებულია"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
+ <string name="home_screen" msgid="806512411299847073">"მთავარი ეკრანი"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"მორგებული ქმედებები"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"შეეხეთ და დააყოვნეთ ვიჯეტის ასარჩევად."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ორმაგად შეეხეთ და გეჭიროთ ვიჯეტის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 009fa4a..7762cd7 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Жүктелген қолданба қауіпсіз режимде өшірілген"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
+ <string name="home_screen" msgid="806512411299847073">"Негізгі экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Арнаулы әрекеттер"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетті таңдау үшін түртіп, мықтап ұстаңыз."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Виджетті таңдау немесе арнаулы әрекеттерді таңдау үшін екі рет түртіп, ұстап тұрыңыз."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 864c979..862a652 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"បានបិទកម្មវិធីដែលបានទាញយកក្នុងរបៀបសុវត្ថិភាព"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"បានបិទធាតុក្រាហ្វិកក្នុងរបៀបសុវត្ថិភាព"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
+ <string name="home_screen" msgid="806512411299847073">"អេក្រង់ដើម"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"សកម្មភាពផ្ទាល់ខ្លួន"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ប៉ះ & សង្កត់ ដើម្បីជ្រើសធាតុក្រាហ្វិក។"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ប៉ះពីរដង ហើយចុចឲ្យជាប់ដើម្បីជ្រើសយកធាតុក្រាហ្វិក ឬប្រើសកម្មភាពផ្ទាល់ខ្លួន។"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 2c50567..55fe36c 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ಡೌನ್ಲೋಡ್ ಮಾಡಲಾದ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="home_screen" msgid="806512411299847073">"ಮುಖಪುಟದ ಪರದೆ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳು"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ವಿಜೆಟ್ ಅನ್ನು ಆರಿಸಿಕೊಳ್ಳಲು ಸ್ಪರ್ಶಿಸಿ & ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ವಿಜೆಟ್ ಆರಿಸಿಕೊಳ್ಳಲು ಹೋಲ್ಡ್ ಮಾಡಿ ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಿ"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1ef438f..bcc1699 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"다운로드한 앱은 안전 모드에서 사용할 수 없습니다."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
+ <string name="home_screen" msgid="806512411299847073">"메인 스크린"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"맞춤 작업"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"위젯을 선택하려면 길게 터치하세요."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"위젯을 선택하려면 두 번 탭한 다음 길게 터치하거나 맞춤 액션을 사용합니다."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 71e02d0..75700ce 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Жүктөп алынган колдонмо Коопсуз режиминде иштен чыгарылды"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
+ <string name="home_screen" msgid="806512411299847073">"Башкы экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ыңгайлаштырылган аракеттер"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетти тандаш үчүн, басып туруңуз"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Виджет тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index e231102..31b7db4 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ແອັບຯທີ່ດາວໂຫລດແລ້ວຖືກປິດການນຳໃຊ້ໃນ Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ວິດເຈັດຖືກປິດໃນ Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
+ <string name="home_screen" msgid="806512411299847073">"ໜ້າຈໍຫຼັກ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ຄຳສັ່ງແບບກຳນົດເອງ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ສຳພັດຄ້າງໄວ້ ເພື່ອຈັບວິດເຈັດ."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ແຕະຄ້າງໄວ້ ເພື່ອເລືອກວິດເຈັດ ຫຼື ໃຊ້ການດຳເນີນການກຳນົດເອງ."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 13ebaa3..1564d45 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Atsisiųsta programa išjungta Saugos režimu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
+ <string name="home_screen" msgid="806512411299847073">"Pagrindinis ekranas"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tinkinti veiksmai"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Palieskite ir laikykite, kad pasirinkt. valdiklį."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dukart palieskite ir laikykite, kad pasirinktumėte valdiklį ar naudotumėte tinkintus veiksmus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 924b399..6458d0b 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Lejupielādētā lietotne ir atspējota drošajā režīmā."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
+ <string name="home_screen" msgid="806512411299847073">"Sākuma ekrāns"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Pielāgotās darbības"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Lai izvēlētos logrīku, pieskarieties un turiet to."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Lai atlasītu logrīku, veiciet dubultskārienu uz tā un turiet to vai arī veiciet pielāgotas darbības."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 7e44707..83da8a1 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Преземената апликација е оневозможена во безбеден режим"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
+ <string name="home_screen" msgid="806512411299847073">"Почетен екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Приспособени дејства"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Допри и задржи за да се избере виџетот."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Допрете двапати и задржете за да изберете додаток или да користите приспособени дејства."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 941d1ac..95a558a 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ഡൗൺലോഡുചെയ്ത അപ്ലിക്കേഷൻ സുരക്ഷാ മോഡിൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
+ <string name="home_screen" msgid="806512411299847073">"ഹോം സ്ക്രീൻ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ഇഷ്ടാനുസൃത പ്രവർത്തനങ്ങൾ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ഒരു വിജറ്റ് ചേർക്കുന്നതിന് അത് സ്പർശിച്ച് പിടിക്കുക."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"വിജറ്റ് തിരഞ്ഞെടുക്കാനോ ഇഷ്ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കാനോ രണ്ടുതവണ ടാപ്പുചെയ്ത് പിടിക്കുക."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 45e1856..d9efe5d 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Татаж авсан апп-г Аюулгүй горим дотроос идэвхгүйжүүлсэн"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
+ <string name="home_screen" msgid="806512411299847073">"Үндсэн нүүр"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Захиалгат үйлдэл"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетийг авах бол хүрээд барина уу."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Жижиг хэрэгсэл авах болон тохируулсан үйлдлийг ашиглахын тулд 2 удаа товшоод барина уу."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 1ec443c..5938b8e 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड केलेला अॅप सुरक्षित मोड मध्ये अक्षम केला"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
+ <string name="home_screen" msgid="806512411299847073">"मुख्यपृष्ठ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"सानुकूल क्रिया"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"एक विजेट निवडण्यासाठी दोनदा टॅप करा आणि धरून ठेवा किंवा सानुकूल क्रिया वापरा."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 215315a..63de9cb 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Apl yang dimuat turun dilumpuhkan dalam mod Selamat"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
+ <string name="home_screen" msgid="806512411299847073">"Skrin utama"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tindakan tersuai"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh & tahan untuk mengambil widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketik dua kali & tahan untuk mengambil widget atau menggunakan tindakan tersuai"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index bc4e484..c65f3ab 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ဒေါင်းလုဒ် အက်ပ်ကို လုံခြုံရေး မုဒ်ထဲမှာ ပိတ်ထား"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
+ <string name="home_screen" msgid="806512411299847073">"ပင်မစာမျက်နှာ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"စိတ်ကြိုက် လုပ်ဆောင်ချက်များ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ဝဒ်ဂျက်တစ်ခုကို ကောက်ယူရန် ဖိနှိပ်ထားပါ"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ဝစ်ဂျက်တစ်ခုကိုရယူရန် သို့မဟုတ် စိတ်ကြိုက်လုပ်ဆောင်မှုများကို အသုံးပြုရန် နှစ်ချက်တို့ပြီး ကိုင်ထားပါ။"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 449094d..38f027a 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"En nedlastet app er deaktivert i sikker modus"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startskjerm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tilpassede handlinger"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Trykk og hold inne for å plukke opp en modul."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dobbelttrykk og hold inne for å velge en modul eller bruke tilpassede handlinger."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index 8accfcc..330352f 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"सुरक्षित मोडमा डाउनलोड गरेको अनुप्रयोग अक्षम गरिएको छ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
+ <string name="home_screen" msgid="806512411299847073">"गृह स्क्रिन"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"आफू अनुकूलका कारबाहीहरू"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"एउटा विजेटलाई टिप्नको लागि टच गरेर होल्ड गर्नुहोस्।"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"विजेटलाई छान्न वा अनुकूलन कार्यहरू प्रयोग गर्न डबल ट्याप गरी होल्ड गर्नुहोस्।"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0134ae1..58f63e6 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Gedownloade app uitgeschakeld in veilige modus"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgeschakeld in Veilige modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startscherm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Aangepaste acties"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Blijf aanraken om een widget toe te voegen."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dubbeltik en blijf aanraken om een widget toe te voegen of aangepaste acties te gebruiken."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index 3be9bec..38769aa 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ਡਾਊਨਲੋਡ ਕੀਤਾ ਐਪ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜਿਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+ <string name="home_screen" msgid="806512411299847073">"ਮੁੱਖ ਸਕ੍ਰੀਨ"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਛੋਹਵੋT & ਹੋਲਡ ਕਰੋ।"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"ਡਬਲ-ਟੈਪ & ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਹੋਲਡ ਕਰੋ ਅਤੇ ਕਸਟਮ ਕਿਰਿਆਵਾਂ ਵਰਤੋ।"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index feb2a36..1e2fc47 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Pobrana aplikacja została wyłączona w trybie awaryjnym"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ekran główny"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Działania niestandardowe"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Aby dodać widżet, kliknij go i przytrzymaj."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Kliknij dwukrotnie i przytrzymaj, by wybrać widżet lub użyć działań niestandardowych."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6d684fa..000c7ad 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicação transferida desativada no Modo de segurança"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ecrã principal"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ações personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Prima sem soltar para escolher um widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toque duas vezes sem soltar para escolher um widget ou utilize ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 82a756f..9969997 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"App transferido por download desativado no modo de segurança"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
+ <string name="home_screen" msgid="806512411299847073">"Tela inicial"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Ações personalizadas"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Toque e pressione para selecionar um widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toque duas vezes e segure para selecionar um widget ou usar ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 5905bcf..6faf57f 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicația descărcată este dezactivată în modul de siguranță"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ecran de pornire"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Acțiuni personalizate"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Atingeți lung un widget pentru a-l alege."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Atingeți de două ori și mențineți apăsat ca să alegeți un widget sau folosiți acțiuni personalizate."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ad30c97..7bc1819 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Скачанное приложение отключено в безопасном режиме"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
+ <string name="home_screen" msgid="806512411299847073">"Главный экран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Пользовательские действия"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Чтобы выбрать виджет, нажмите на значок и удерживайте его."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Чтобы выбрать виджет, нажмите на него дважды и не отпускайте или выполните предложенные действия."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 1e0ed28..df4b411 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ආරක්ෂිත ආකාරය තුළ බාගන්න ලද යෙදුම් අබල කරන්න"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
+ <string name="home_screen" msgid="806512411299847073">"මුල් පිටු තිරය"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"අභිරුචි ක්රියා"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"විජට් එක ස්පර්ශ කර අහුලා ගැනීමට අල්ලාගෙන සිටින්න."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"විජට් එකක් අහුලා ගැනීමට හෝ අභිරුචි ක්රියා කිරීමට ඩබල් ටැප් කර අල්ලා ගෙන සිටින්න."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7210d79..d0a2e59 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Stiahnutá aplikácia je v núdzovom režime zakázaná"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
+ <string name="home_screen" msgid="806512411299847073">"Plocha"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Vlastné akcie"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Miniaplikáciu pridáte stlačením a podržaním."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Miniaplikáciu pridáte dvojitým klepnutím a pridržaním alebo pomocou vlastných akcií."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b55cb0d..0682f8d 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Prenesena aplikacija je onemogočena v Varnem načinu"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
+ <string name="home_screen" msgid="806512411299847073">"Začetni zaslon"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Dejanja po meri"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Za izbiro pripomočka se ga dotaknite in pridržite."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Če želite izbrati pripomoček ali uporabiti dejanja po meri, se ga dvakrat dotaknite in ga pridržite."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index c4fbaf3..701eaf8 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikacioni i shkarkuar është i çaktivizuar në modalitetin e sigurt"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ekrani bazë"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Veprimet e personalizuara"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Prek dhe mbaj shtypur për të zgjedhur një miniaplikacion."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Prek dy herë dhe mbaj shtypur për të zgjedhur një miniaplikacion ose për të përdorur veprimet e personalizuara."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 67a63cf..a396f18 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Преузета апликација је онемогућена у Безбедном режиму"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
+ <string name="home_screen" msgid="806512411299847073">"Почетни екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Прилагођене радње"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Додирните и задржите да бисте изабрали виџет."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Двапут додирните и задржите да бисте изабрали виџет или користите прилагођене радње."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index dfcc1b0..907de79 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Den hämtade appen inaktiverades i säkert läge"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
+ <string name="home_screen" msgid="806512411299847073">"Startskärm"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Anpassade åtgärder"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryck länge om du vill flytta en widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tryck två gånger och håll kvar om du vill ta upp en widget eller använda anpassade åtgärder."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 03c8f06..20654e0 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Programu iliyopakuliwa imezimwa katika Hali Salama"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
+ <string name="home_screen" msgid="806512411299847073">"Skrini ya kwanza"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Vitendo maalum"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Gusa na ushikilie ili kuteua wijeti."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Gonga mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2838088..ead666c 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -16,8 +16,6 @@
<resources>
<!-- All Apps -->
- <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
- <dimen name="all_apps_grid_section_text_size">26sp</dimen>
<dimen name="all_apps_background_canvas_width">850dp</dimen>
<dimen name="all_apps_background_canvas_height">525dp</dimen>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 7db0ae4..fd51b71 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"இறக்கிய பயன்பாடு பாதுகாப்பு முறையில் முடக்கப்பட்டது"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"குறுக்குவழி இல்லை"</string>
+ <string name="home_screen" msgid="806512411299847073">"முகப்புத் திரை"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"தனிப்பயன் செயல்கள்"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"விட்ஜெட்டைத் தேர்வுசெய்ய தொட்டுப் பிடிக்கவும்."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"விட்ஜெட்டைத் தேர்ந்தெடுக்க இருமுறை தட்டிப் பிடிக்கவும் அல்லது தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 4e8b86f..ba19ddc 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"డౌన్లోడ్ చేసిన అనువర్తనం సురక్షిత మోడ్లో నిలిపివేయబడింది"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్లో విడ్జెట్లు నిలిపివేయబడ్డాయి"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"సత్వరమార్గం అందుబాటులో లేదు"</string>
+ <string name="home_screen" msgid="806512411299847073">"హోమ్ స్క్రీన్"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"అనుకూల చర్యలు"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"విడ్జెట్ను ఎంచుకోవడానికి తాకి & నొక్కి పెట్టండి."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"విడ్జెట్ను ఎంచుకోవడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కి, ఉంచండి."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 90dec72..4bcda7a 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"แอปที่ดาวน์โหลดถูกปิดในโหมดปลอดภัย"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
+ <string name="home_screen" msgid="806512411299847073">"หน้าจอหลัก"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"การทำงานที่กำหนดเอง"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"แตะค้างเพื่อรับวิดเจ็ต"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"แตะ 2 ครั้งค้างไว้เพื่อเลือกวิดเจ็ตหรือใช้การกระทำที่กำหนดเอง"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index b944ebc..55e02e9 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Naka-disable ang na-download na app sa Safe mode"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Mga custom na pagkilos"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Pindutin nang matagal upang kumuha ng widget."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"I-double tap nang matagal upang pumili ng widget o gumamit ng mga custom na pagkilos."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index e95ee2d..f30b559 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"İndirilen uygulama Güvenli modda devre dışı bırakıldı"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
+ <string name="home_screen" msgid="806512411299847073">"Ana ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Özel işlemler"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget seçmek için dokunun ve basılı tutun."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Bir widget\'ı seçmek veya özel işlemleri kullanmak için iki kez hafifçe dokunun ve basılı tutun."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 1f73f04..750b6a0 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Завантажений додаток вимкнено в безпечному режимі"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
+ <string name="home_screen" msgid="806512411299847073">"Головний екран"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Спеціальні дії"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Натисніть і утримуйте, щоб вибрати віджет."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Двічі натисніть і утримуйте, щоб вибрати віджет, або виконайте іншу дію."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index e4c637d..b285de0 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ڈاؤن لوڈ کردہ ایپ کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
+ <string name="home_screen" msgid="806512411299847073">"ہوم اسکرین"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"حسب ضرورت کارروائیاں"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"کوئی ویجیٹ منتخب کرنے کیلئے ٹچ کریں اور پکڑے رہیں۔"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"کوئی ویجٹ منتخب کرنے یا حسب ضرورت کاروائیاں استعمال کرنے کیلئے دو بار تھپتھپائیں اور پکڑے رکھیں۔"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 0e541d3..77b7367 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Yuklab olingan ilova xavfsiz rejimda o‘chirib qo‘yildi"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
+ <string name="home_screen" msgid="806512411299847073">"Bosh ekran"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Maxsus amallar"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidjetni tanlash uchun bosib turing."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ikki marta bosib va bosib turgan holatda vidjetni tanlang yoki maxsus amaldan foydalaning."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2bf5ad6..2d953e6 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Ứng dụng đã tải xuống bị tắt ở chế độ An toàn"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích con bị vô hiệu hóa ở chế độ an toàn"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
+ <string name="home_screen" msgid="806512411299847073">"Màn hình chính"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Tác vụ tùy chỉnh"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích con."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Nhấn đúp và giữ để chọn tiện ích hoặc sử dụng tác vụ tùy chỉnh."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e3ed354..7967812 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"安全模式下不允许使用下载的此应用"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用小部件"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
+ <string name="home_screen" msgid="806512411299847073">"主屏幕"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"自定义操作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"触摸并按住小部件即可选择。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"点按两次并按住小部件即可选择小部件,您也可以使用自定义操作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ad0f99a..236fc42 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"在安全模式中無法使用「已下載的應用程式」功能"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
+ <string name="home_screen" msgid="806512411299847073">"主畫面"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"自訂操作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"連扲兩下,然後扲住,就可以新增小工具,或者執行自訂操作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 24f391d..8d4c744 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"在安全模式中無法使用「已下載的應用程式」功能"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
+ <string name="home_screen" msgid="806512411299847073">"主螢幕"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"自訂動作"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"輕觸兩下並按住小工具即可選取,您也可以使用自訂動作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 10fbef4..88d4295 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,6 +27,8 @@
<string name="safemode_shortcut_error" msgid="9160126848219158407">"Uhlelo lokusebenza olulandiwe lukhutshaziwe kumodi ephephile"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
+ <string name="home_screen" msgid="806512411299847073">"Isikrini sasekhaya"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Izenzo zangokwezifiso"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Thinta uphinde ubambe ukuze uphakamise iwijethi."</string>
<string name="long_accessible_way_to_add" msgid="4289502106628154155">"Thepha kabili bese uyabamba ukuze uthathe iwijethi noma sebenzisa izenzo ezingokwezifiso."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3423835..32bccb8 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -27,6 +27,7 @@
<enum name="all_apps" value="1" />
<enum name="folder" value="2" />
<enum name="widget_section" value="3" />
+ <enum name="shortcut_popup" value="4" />
</attr>
<attr name="deferShadowGeneration" format="boolean" />
<attr name="customShadows" format="boolean" />
diff --git a/res/values/config.xml b/res/values/config.xml
index a942f02..5b3ee46 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -91,6 +91,9 @@
<!-- View ID used by cell layout to jail its content -->
<item type="id" name="cell_layout_jail_id" />
+ <!-- View ID used by PreviewImageView to cache its instance -->
+ <item type="id" name="preview_image_id" />
+
<!-- Deep shortcuts -->
<integer name="config_deepShortcutOpenDuration">220</integer>
<integer name="config_deepShortcutArrowOpenDuration">80</integer>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index eff9d21..188f98f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -33,8 +33,6 @@
<dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
<!-- Minimum space between workspace and hotseat in spring loaded mode -->
<dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
- <dimen name="dynamic_grid_container_land_left_padding">118dp</dimen>
- <dimen name="dynamic_grid_container_land_right_padding">66dp</dimen>
<!-- Drop target bar -->
<dimen name="dynamic_grid_drop_target_size">48dp</dimen>
@@ -54,6 +52,7 @@
<dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
<dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+ <dimen name="container_fastscroll_popup_margin">18dp</dimen>
<dimen name="container_fastscroll_thumb_height">72dp</dimen>
<dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
<dimen name="container_fastscroll_popup_size">72dp</dimen>
@@ -61,11 +60,8 @@
<!-- All Apps -->
<dimen name="all_apps_button_scale_down">0dp</dimen>
- <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
- <dimen name="all_apps_grid_section_y_offset">8dp</dimen>
- <dimen name="all_apps_grid_section_text_size">24sp</dimen>
+ <dimen name="all_apps_search_bar_field_height">48dp</dimen>
<dimen name="all_apps_search_bar_height">60dp</dimen>
- <dimen name="all_apps_search_bar_margin_top">12dp</dimen>
<dimen name="all_apps_search_bar_icon_margin_right">4dp</dimen>
<dimen name="all_apps_search_bar_icon_margin_top">1dp</dimen>
<dimen name="all_apps_list_bottom_padding">8dp</dimen>
@@ -165,7 +161,7 @@
<dimen name="bg_pill_height">48dp</dimen>
<dimen name="bg_pill_radius">24dp</dimen>
<dimen name="deep_shortcuts_spacing">4dp</dimen>
- <dimen name="deferred_drag_view_scale">6dp</dimen>
+ <dimen name="pre_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
<dimen name="deep_shortcut_icon_size">36dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 60a37e5..a9c970d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,6 +37,10 @@
<string name="safemode_widget_error">Widgets disabled in Safe mode</string>
<!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
<string name="shortcut_not_available">Shortcut isn\'t available</string>
+ <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
+ <string name="home_screen">Home screen</string>
+ <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
+ <string name="custom_actions">Custom actions</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index cd06b75..4e70f43 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -30,6 +30,20 @@
<style name="Theme" parent="@style/LauncherTheme"></style>
+ <style name="FastScrollerPopup" >
+ <item name="android:background">@drawable/container_fastscroll_popup_bg</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:minWidth">@dimen/container_fastscroll_popup_size</item>
+ <item name="android:layout_height">@dimen/container_fastscroll_popup_size</item>
+ <item name="android:textSize">@dimen/container_fastscroll_popup_text_size</item>
+ <item name="android:gravity">center</item>
+ <item name="android:alpha">0</item>
+ <item name="android:elevation">3dp</item>
+ <item name="android:saveEnabled">false</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:includeFontPadding">false</item>
+ </style>
+
<!-- Theme for the widget container. Overridden on API 25. -->
<style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="colorSecondary">@color/fallback_secondary_color</item>
@@ -74,7 +88,6 @@
<item name="android:background">@null</item>
<item name="android:textColor">@color/quantum_panel_text_color</item>
<item name="android:shadowRadius">0</item>
- <item name="android:textSize">@dimen/folder_child_text_size</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:includeFontPadding">false</item>
<item name="customShadows">false</item>
@@ -95,6 +108,7 @@
<item name="android:shadowRadius">0</item>
<item name="customShadows">false</item>
<item name="layoutHorizontal">true</item>
+ <item name="iconDisplay">shortcut_popup</item>
<item name="iconSizeOverride">@dimen/deep_shortcut_icon_size</item>
</style>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
new file mode 100644
index 0000000..65da002
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.dragndrop.DragLayer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for a View which shows a floating UI on top of the launcher UI.
+ */
+public abstract class AbstractFloatingView extends LinearLayout {
+
+ @IntDef(flag = true, value = {TYPE_FOLDER, TYPE_DEEPSHORTCUT_CONTAINER})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FloatingViewType {}
+ public static final int TYPE_FOLDER = 1 << 0;
+ public static final int TYPE_DEEPSHORTCUT_CONTAINER = 1 << 1;
+
+ protected boolean mIsOpen;
+
+ public AbstractFloatingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public final void close(boolean animate) {
+ animate &= !Utilities.isPowerSaverOn(getContext());
+ handleClose(animate);
+ Launcher.getLauncher(getContext()).getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+
+ protected abstract void handleClose(boolean animate);
+
+ /**
+ * If the view is current handling keyboard, return the active target, null otherwise
+ */
+ public ExtendedEditText getActiveTextView() {
+ return null;
+ }
+
+
+ /**
+ * Any additional view (outside of this container) where touch should be allowed while this
+ * view is visible.
+ */
+ public View getExtendedTouchView() {
+ return null;
+ }
+
+ public final boolean isOpen() {
+ return mIsOpen;
+ }
+
+ protected abstract boolean isOfType(@FloatingViewType int type);
+
+ protected static <T extends AbstractFloatingView> T getOpenView(
+ Launcher launcher, @FloatingViewType int type) {
+ DragLayer dragLayer = launcher.getDragLayer();
+ // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+ // and will be one of the last views.
+ for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+ View child = dragLayer.getChildAt(i);
+ if (child instanceof AbstractFloatingView) {
+ AbstractFloatingView view = (AbstractFloatingView) child;
+ if (view.isOfType(type) && view.isOpen()) {
+ return (T) view;
+ }
+ }
+ }
+ return null;
+ }
+
+ protected static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
+ AbstractFloatingView view = getOpenView(launcher, type);
+ if (view != null) {
+ view.close(true);
+ }
+ }
+
+ public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+ DragLayer dragLayer = launcher.getDragLayer();
+ // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+ // and will be one of the last views.
+ for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+ View child = dragLayer.getChildAt(i);
+ if (child instanceof AbstractFloatingView) {
+ ((AbstractFloatingView) child).close(animate);
+ }
+ }
+ }
+
+ public static void closeAllOpenViews(Launcher launcher) {
+ closeAllOpenViews(launcher, true);
+ }
+
+ public static AbstractFloatingView getTopOpenView(Launcher launcher) {
+ return getOpenView(launcher, TYPE_FOLDER | TYPE_DEEPSHORTCUT_CONTAINER);
+ }
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index c431593..b13c20b 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -23,7 +23,7 @@
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.ItemInfoMatcher;
import java.util.ArrayList;
import java.util.HashSet;
@@ -33,7 +33,7 @@
/**
* Stores the list of all applications for the all apps view.
*/
-class AllAppsList {
+public class AllAppsList {
public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
/** The list off all apps. */
@@ -112,8 +112,7 @@
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
- final ComponentName component = info.intent.getComponent();
- if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
+ if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
removed.add(info);
data.remove(i);
}
@@ -121,14 +120,13 @@
}
/**
- * Updates the apps for the given packageName and user based on {@param op}.
+ * Updates the disabled flags of apps matching {@param matcher} based on {@param op}.
*/
- public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) {
+ public void updateDisabledFlags(ItemInfoMatcher matcher, FlagOp op) {
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
- final ComponentName component = info.intent.getComponent();
- if (info.user.equals(user) && pkgFilter.matches(component.getPackageName())) {
+ if (matcher.matches(info, info.componentName)) {
info.isDisabled = op.apply(info.isDisabled);
modified.add(info);
}
@@ -157,10 +155,9 @@
// to the removed list.
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
- final ComponentName component = applicationInfo.intent.getComponent();
if (user.equals(applicationInfo.user)
- && packageName.equals(component.getPackageName())) {
- if (!findActivity(matches, component)) {
+ && packageName.equals(applicationInfo.componentName.getPackageName())) {
+ if (!findActivity(matches, applicationInfo.componentName)) {
removed.add(applicationInfo);
data.remove(i);
}
@@ -184,11 +181,10 @@
// Remove all data for this package.
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
- final ComponentName component = applicationInfo.intent.getComponent();
if (user.equals(applicationInfo.user)
- && packageName.equals(component.getPackageName())) {
+ && packageName.equals(applicationInfo.componentName.getPackageName())) {
removed.add(applicationInfo);
- mIconCache.remove(component, user);
+ mIconCache.remove(applicationInfo.componentName, user);
data.remove(i);
}
}
@@ -240,9 +236,8 @@
private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
String className) {
for (AppInfo info: data) {
- final ComponentName component = info.intent.getComponent();
- if (user.equals(info.user) && packageName.equals(component.getPackageName())
- && className.equals(component.getClassName())) {
+ if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName())
+ && className.equals(info.componentName.getClassName())) {
return info;
}
}
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
index 0e18874..7074f78 100644
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ b/src/com/android/launcher3/AnotherWindowDropTarget.java
@@ -27,7 +27,7 @@
public class AnotherWindowDropTarget implements DropTarget {
final Launcher mLauncher;
- public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
+ public AnotherWindowDropTarget (Context context) { mLauncher = Launcher.getLauncher(context); }
@Override
public boolean isDropEnabled() { return true; }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 4c4d67c..3b22f46 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -52,11 +52,6 @@
public ComponentName componentName;
- static final int DOWNLOADED_FLAG = 1;
- static final int UPDATED_SYSTEM_APP_FLAG = 2;
-
- int flags = 0;
-
/**
* {@see ShortcutInfo#isDisabled}
*/
@@ -88,7 +83,6 @@
IconCache iconCache, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = ItemInfo.NO_ID;
- flags = initFlags(info);
if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
}
@@ -101,25 +95,11 @@
this.user = user;
}
- public static int initFlags(LauncherActivityInfoCompat info) {
- int appFlags = info.getApplicationInfo().flags;
- int flags = 0;
- if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
- flags |= DOWNLOADED_FLAG;
-
- if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
- flags |= UPDATED_SYSTEM_APP_FLAG;
- }
- }
- return flags;
- }
-
public AppInfo(AppInfo info) {
super(info);
componentName = info.componentName;
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
- flags = info.flags;
isDisabled = info.isDisabled;
iconBitmap = info.iconBitmap;
}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index cd27b4c..0380923 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -1,7 +1,5 @@
package com.android.launcher3;
-import com.android.launcher3.dragndrop.DragLayer;
-
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -13,16 +11,19 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
-import android.view.Gravity;
+import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.FocusLogic;
+import com.android.launcher3.util.TouchController;
-public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListener {
+public class AppWidgetResizeFrame extends FrameLayout
+ implements View.OnKeyListener, TouchController {
private static final int SNAP_DURATION = 150;
private static final float DIMMED_HANDLE_ALPHA = 0f;
private static final float RESIZE_THRESHOLD = 0.66f;
@@ -32,17 +33,22 @@
// Represents the cell size on the grid in the two orientations.
private static Point[] sCellSize;
+ private static final int HANDLE_COUNT = 4;
+ private static final int INDEX_LEFT = 0;
+ private static final int INDEX_TOP = 1;
+ private static final int INDEX_RIGHT = 2;
+ private static final int INDEX_BOTTOM = 3;
+
private final Launcher mLauncher;
- private final LauncherAppWidgetHostView mWidgetView;
- private final CellLayout mCellLayout;
- private final DragLayer mDragLayer;
+ private final DragViewStateAnnouncer mStateAnnouncer;
- private final ImageView mLeftHandle;
- private final ImageView mRightHandle;
- private final ImageView mTopHandle;
- private final ImageView mBottomHandle;
+ private final View[] mDragHandles = new View[HANDLE_COUNT];
- private final Rect mWidgetPadding;
+ private LauncherAppWidgetHostView mWidgetView;
+ private CellLayout mCellLayout;
+ private DragLayer mDragLayer;
+
+ private Rect mWidgetPadding;
private final int mBackgroundPadding;
private final int mTouchTargetWidth;
@@ -51,17 +57,20 @@
private final int[] mLastDirectionVector = new int[2];
private final int[] mTmpPt = new int[2];
- private final DragViewStateAnnouncer mStateAnnouncer;
+ private final IntRange mTempRange1 = new IntRange();
+ private final IntRange mTempRange2 = new IntRange();
+
+ private final IntRange mDeltaXRange = new IntRange();
+ private final IntRange mBaselineX = new IntRange();
+
+ private final IntRange mDeltaYRange = new IntRange();
+ private final IntRange mBaselineY = new IntRange();
private boolean mLeftBorderActive;
private boolean mRightBorderActive;
private boolean mTopBorderActive;
private boolean mBottomBorderActive;
- private int mBaselineWidth;
- private int mBaselineHeight;
- private int mBaselineX;
- private int mBaselineY;
private int mResizeMode;
private int mRunningHInc;
@@ -76,11 +85,38 @@
private int mTopTouchRegionAdjustment = 0;
private int mBottomTouchRegionAdjustment = 0;
- public AppWidgetResizeFrame(Context context,
- LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
+ private int mXDown, mYDown;
- super(context);
+ public AppWidgetResizeFrame(Context context) {
+ this(context, null);
+ }
+
+ public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
mLauncher = Launcher.getLauncher(context);
+ mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
+
+ mBackgroundPadding = getResources()
+ .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
+ mTouchTargetWidth = 2 * mBackgroundPadding;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ for (int i = 0; i < HANDLE_COUNT; i ++) {
+ mDragHandles[i] = getChildAt(i);
+ }
+ }
+
+ public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
+ DragLayer dragLayer) {
mCellLayout = cellLayout;
mWidgetView = widgetView;
LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
@@ -91,63 +127,23 @@
mMinHSpan = info.minSpanX;
mMinVSpan = info.minSpanY;
- mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
-
- setBackgroundResource(R.drawable.widget_resize_shadow);
- setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
- setPadding(0, 0, 0, 0);
-
- final int handleMargin = getResources().getDimensionPixelSize(R.dimen.widget_handle_margin);
- LayoutParams lp;
- mLeftHandle = new ImageView(context);
- mLeftHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.LEFT | Gravity.CENTER_VERTICAL);
- lp.leftMargin = handleMargin;
- addView(mLeftHandle, lp);
-
- mRightHandle = new ImageView(context);
- mRightHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.RIGHT | Gravity.CENTER_VERTICAL);
- lp.rightMargin = handleMargin;
- addView(mRightHandle, lp);
-
- mTopHandle = new ImageView(context);
- mTopHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_HORIZONTAL | Gravity.TOP);
- lp.topMargin = handleMargin;
- addView(mTopHandle, lp);
-
- mBottomHandle = new ImageView(context);
- mBottomHandle.setImageResource(R.drawable.ic_widget_resize_handle);
- lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
- lp.bottomMargin = handleMargin;
- addView(mBottomHandle, lp);
-
if (!info.isCustomWidget) {
- mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context,
+ mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
widgetView.getAppWidgetInfo().provider, null);
} else {
- Resources r = context.getResources();
+ Resources r = getContext().getResources();
int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding);
mWidgetPadding = new Rect(padding, padding, padding, padding);
}
if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
- mTopHandle.setVisibility(GONE);
- mBottomHandle.setVisibility(GONE);
+ mDragHandles[INDEX_TOP].setVisibility(GONE);
+ mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
} else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
- mLeftHandle.setVisibility(GONE);
- mRightHandle.setVisibility(GONE);
+ mDragHandles[INDEX_LEFT].setVisibility(GONE);
+ mDragHandles[INDEX_RIGHT].setVisibility(GONE);
}
- mBackgroundPadding = getResources()
- .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
- mTouchTargetWidth = 2 * mBackgroundPadding;
-
// When we create the resize frame, we first mark all cells as unoccupied. The appropriate
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
@@ -169,101 +165,74 @@
boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
|| mTopBorderActive || mBottomBorderActive;
- mBaselineWidth = getMeasuredWidth();
- mBaselineHeight = getMeasuredHeight();
- mBaselineX = getLeft();
- mBaselineY = getTop();
-
if (anyBordersActive) {
- mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
- mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
- mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
- mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
}
+
+ if (mLeftBorderActive) {
+ mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
+ } else if (mRightBorderActive) {
+ mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
+ } else {
+ mDeltaXRange.set(0, 0);
+ }
+ mBaselineX.set(getLeft(), getRight());
+
+ if (mTopBorderActive) {
+ mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
+ } else if (mBottomBorderActive) {
+ mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
+ } else {
+ mDeltaYRange.set(0, 0);
+ }
+ mBaselineY.set(getTop(), getBottom());
+
return anyBordersActive;
}
/**
- * Here we bound the deltas such that the frame cannot be stretched beyond the extents
- * of the CellLayout, and such that the frame's borders can't cross.
+ * Based on the deltas, we resize the frame.
*/
- public void updateDeltas(int deltaX, int deltaY) {
- if (mLeftBorderActive) {
- mDeltaX = Math.max(-mBaselineX, deltaX);
- mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
- } else if (mRightBorderActive) {
- mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
- mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
- }
-
- if (mTopBorderActive) {
- mDeltaY = Math.max(-mBaselineY, deltaY);
- mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
- } else if (mBottomBorderActive) {
- mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
- mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
- }
- }
-
public void visualizeResizeForDelta(int deltaX, int deltaY) {
- visualizeResizeForDelta(deltaX, deltaY, false);
+ mDeltaX = mDeltaXRange.clamp(deltaX);
+ mDeltaY = mDeltaYRange.clamp(deltaY);
+
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ mDeltaX = mDeltaXRange.clamp(deltaX);
+ mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
+ lp.x = mTempRange1.start;
+ lp.width = mTempRange1.size();
+
+ mDeltaY = mDeltaYRange.clamp(deltaY);
+ mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
+ lp.y = mTempRange1.start;
+ lp.height = mTempRange1.size();
+
+ resizeWidgetIfNeeded(false);
+ requestLayout();
}
- /**
- * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
- */
- private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
- updateDeltas(deltaX, deltaY);
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-
- if (mLeftBorderActive) {
- lp.x = mBaselineX + mDeltaX;
- lp.width = mBaselineWidth - mDeltaX;
- } else if (mRightBorderActive) {
- lp.width = mBaselineWidth + mDeltaX;
- }
-
- if (mTopBorderActive) {
- lp.y = mBaselineY + mDeltaY;
- lp.height = mBaselineHeight - mDeltaY;
- } else if (mBottomBorderActive) {
- lp.height = mBaselineHeight + mDeltaY;
- }
-
- resizeWidgetIfNeeded(onDismiss);
- requestLayout();
+ private static int getSpanIncrement(float deltaFrac) {
+ return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
}
/**
* Based on the current deltas, we determine if and how to resize the widget.
*/
private void resizeWidgetIfNeeded(boolean onDismiss) {
- int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
- int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+ float xThreshold = mCellLayout.getCellWidth();
+ float yThreshold = mCellLayout.getCellHeight();
- int deltaX = mDeltaX + mDeltaXAddOn;
- int deltaY = mDeltaY + mDeltaYAddOn;
-
- float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
- float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
-
- int hSpanInc = 0;
- int vSpanInc = 0;
- int cellXInc = 0;
- int cellYInc = 0;
-
- int countX = mCellLayout.getCountX();
- int countY = mCellLayout.getCountY();
-
- if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
- hSpanInc = Math.round(hSpanIncF);
- }
- if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
- vSpanInc = Math.round(vSpanIncF);
- }
+ int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
+ int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
+ mDirectionVector[0] = 0;
+ mDirectionVector[1] = 0;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
@@ -272,55 +241,24 @@
int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
- int hSpanDelta = 0;
- int vSpanDelta = 0;
-
// For each border, we bound the resizing based on the minimum width, and the maximum
// expandability.
- if (mLeftBorderActive) {
- cellXInc = Math.max(-cellX, hSpanInc);
- cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
- hSpanInc *= -1;
- hSpanInc = Math.min(cellX, hSpanInc);
- hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
- hSpanDelta = -hSpanInc;
-
- } else if (mRightBorderActive) {
- hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
- hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
- hSpanDelta = hSpanInc;
+ mTempRange1.set(cellX, spanX + cellX);
+ int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
+ hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
+ cellX = mTempRange2.start;
+ spanX = mTempRange2.size();
+ if (hSpanDelta != 0) {
+ mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
}
- if (mTopBorderActive) {
- cellYInc = Math.max(-cellY, vSpanInc);
- cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
- vSpanInc *= -1;
- vSpanInc = Math.min(cellY, vSpanInc);
- vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
- vSpanDelta = -vSpanInc;
- } else if (mBottomBorderActive) {
- vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
- vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
- vSpanDelta = vSpanInc;
- }
-
- mDirectionVector[0] = 0;
- mDirectionVector[1] = 0;
- // Update the widget's dimensions and position according to the deltas computed above
- if (mLeftBorderActive || mRightBorderActive) {
- spanX += hSpanInc;
- cellX += cellXInc;
- if (hSpanDelta != 0) {
- mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
- }
- }
-
- if (mTopBorderActive || mBottomBorderActive) {
- spanY += vSpanInc;
- cellY += cellYInc;
- if (vSpanDelta != 0) {
- mDirectionVector[1] = mTopBorderActive ? -1 : 1;
- }
+ mTempRange1.set(cellY, spanY + cellY);
+ int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
+ vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
+ cellY = mTempRange2.start;
+ spanY = mTempRange2.size();
+ if (vSpanDelta != 0) {
+ mDirectionVector[1] = mTopBorderActive ? -1 : 1;
}
if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
@@ -398,9 +336,9 @@
requestLayout();
}
- public void onTouchUp() {
- int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
- int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+ private void onTouchUp() {
+ int xThreshold = mCellLayout.getCellWidth();
+ int yThreshold = mCellLayout.getCellHeight();
mDeltaXAddOn = mRunningHInc * xThreshold;
mDeltaYAddOn = mRunningVInc * yThreshold;
@@ -450,10 +388,9 @@
lp.height = newHeight;
lp.x = newX;
lp.y = newY;
- mLeftHandle.setAlpha(1.0f);
- mRightHandle.setAlpha(1.0f);
- mTopHandle.setAlpha(1.0f);
- mBottomHandle.setAlpha(1.0f);
+ for (int i = 0; i < HANDLE_COUNT; i++) {
+ mDragHandles[i].setAlpha(1.0f);
+ }
requestLayout();
} else {
PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
@@ -463,22 +400,15 @@
PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
ObjectAnimator oa =
LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
- ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f);
- ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f);
- ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f);
- ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f);
oa.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
requestLayout();
}
});
AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
- if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
- set.playTogether(oa, topOa, bottomOa);
- } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
- set.playTogether(oa, leftOa, rightOa);
- } else {
- set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
+ set.play(oa);
+ for (int i = 0; i < HANDLE_COUNT; i++) {
+ set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
}
set.setDuration(SNAP_DURATION);
@@ -493,10 +423,115 @@
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Clear the frame and give focus to the widget host view when a directional key is pressed.
if (FocusLogic.shouldConsume(keyCode)) {
- mDragLayer.clearAllResizeFrames();
+ mDragLayer.clearResizeFrame();
mWidgetView.requestFocus();
return true;
}
return false;
}
+
+ private boolean handleTouchDown(MotionEvent ev) {
+ Rect hitRect = new Rect();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ getHitRect(hitRect);
+ if (hitRect.contains(x, y)) {
+ if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
+ mXDown = x;
+ mYDown = y;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ int action = ev.getAction();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ return handleTouchDown(ev);
+ case MotionEvent.ACTION_MOVE:
+ visualizeResizeForDelta(x - mXDown, y - mYDown);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ visualizeResizeForDelta(x - mXDown, y - mYDown);
+ onTouchUp();
+ mXDown = mYDown = 0;
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * A mutable class for describing the range of two int values.
+ */
+ private static class IntRange {
+
+ public int start, end;
+
+ public int clamp(int value) {
+ return Utilities.boundToRange(value, start, end);
+ }
+
+ public void set(int s, int e) {
+ start = s;
+ end = e;
+ }
+
+ public int size() {
+ return end - start;
+ }
+
+ /**
+ * Moves either the start or end edge (but never both) by {@param delta} and sets the
+ * result in {@param out}
+ */
+ public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
+ out.start = moveStart ? start + delta : start;
+ out.end = moveEnd ? end + delta : end;
+ }
+
+ /**
+ * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
+ * with extra conditions.
+ * @param minSize minimum size after with the moving edge should not be shifted any further.
+ * For eg, if delta = -3 when moving the endEdge brings the size to less than
+ * minSize, only delta = -2 will applied
+ * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
+ * @return the amount of increase when endEdge was moves and the amount of decrease when
+ * the start edge was moved.
+ */
+ public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
+ int minSize, int maxEnd, IntRange out) {
+ applyDelta(moveStart, moveEnd, delta, out);
+ if (start < 0) {
+ out.start = 0;
+ }
+ if (end > maxEnd) {
+ out.end = maxEnd;
+ }
+ if (out.size() < minSize) {
+ if (moveStart) {
+ out.start = out.end - minSize;
+ } else if (moveEnd) {
+ out.end = out.start + minSize;
+ }
+ }
+ return moveEnd ? out.size() - size() : size() - out.size();
+ }
+ }
}
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index d5309b4..2a4212a 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
@@ -38,6 +37,7 @@
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
@@ -436,7 +436,8 @@
return -1;
}
- ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext));
+ mValues.put(LauncherSettings.Favorites.ICON,
+ Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext)));
mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index 96942ee..ac7cbaf 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -16,17 +16,23 @@
package com.android.launcher3;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.TransformingTouchDelegate;
/**
* A base container view, which supports resizing.
@@ -34,17 +40,17 @@
public abstract class BaseContainerView extends FrameLayout
implements DeviceProfile.LauncherLayoutChangeListener {
- protected int mContainerPaddingLeft;
- protected int mContainerPaddingRight;
- protected int mContainerPaddingTop;
- protected int mContainerPaddingBottom;
+ private static final Rect sBgPaddingRect = new Rect();
- private InsetDrawable mRevealDrawable;
protected final Drawable mBaseDrawable;
private View mRevealView;
private View mContent;
+ private TransformingTouchDelegate mTouchDelegate;
+
+ private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f);
+
public BaseContainerView(Context context) {
this(context, null);
}
@@ -72,6 +78,12 @@
DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
grid.addLauncherLayoutChangedListener(this);
+
+ View touchDelegateTargetView = getTouchDelegateTargetView();
+ if (touchDelegateTargetView != null) {
+ mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView);
+ ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate);
+ }
}
@Override
@@ -97,6 +109,65 @@
updatePaddings();
}
+ /**
+ * Calculate the background padding as it can change due to insets/content padding change.
+ */
+ private void updatePaddings() {
+ Context context = getContext();
+ int paddingLeft;
+ int paddingRight;
+ int paddingTop;
+ int paddingBottom;
+
+ DeviceProfile grid = Launcher.getLauncher(context).getDeviceProfile();
+ int[] padding = grid.getContainerPadding();
+ paddingLeft = padding[0] + grid.edgeMarginPx;
+ paddingRight = padding[1] + grid.edgeMarginPx;
+ if (!grid.isVerticalBarLayout()) {
+ paddingTop = paddingBottom = grid.edgeMarginPx;
+ } else {
+ paddingTop = paddingBottom = 0;
+ }
+ updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+
+ /**
+ * Update the background for the reveal view and content view based on the background padding.
+ */
+ protected void updateBackground(int paddingLeft, int paddingTop,
+ int paddingRight, int paddingBottom) {
+ mRevealView.setBackground(new InsetDrawable(mBaseDrawable,
+ paddingLeft, paddingTop, paddingRight, paddingBottom));
+ mContent.setBackground(new InsetDrawable(mBaseDrawable,
+ paddingLeft, paddingTop, paddingRight, paddingBottom));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ View touchDelegateTargetView = getTouchDelegateTargetView();
+ if (touchDelegateTargetView != null) {
+ getRevealView().getBackground().getPadding(sBgPaddingRect);
+ mTouchDelegate.setBounds(
+ touchDelegateTargetView.getLeft() - sBgPaddingRect.left,
+ touchDelegateTargetView.getTop() - sBgPaddingRect.top,
+ touchDelegateTargetView.getRight() + sBgPaddingRect.right,
+ touchDelegateTargetView.getBottom() + sBgPaddingRect.bottom);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
public void setRevealDrawableColor(int color) {
((ColorDrawable) mBaseDrawable).setColor(color);
}
@@ -109,35 +180,41 @@
return mRevealView;
}
- private void updatePaddings() {
- Context context = getContext();
- Launcher launcher = Launcher.getLauncher(context);
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
- this instanceof AllAppsContainerView &&
- !launcher.getDeviceProfile().isVerticalBarLayout()) {
- mContainerPaddingLeft = mContainerPaddingRight = 0;
- mContainerPaddingTop = mContainerPaddingBottom = 0;
- } else {
- DeviceProfile grid = launcher.getDeviceProfile();
- int[] padding = grid.getContainerPadding(context);
- mContainerPaddingLeft = padding[0] + grid.edgeMarginPx;
- mContainerPaddingRight = padding[1] + grid.edgeMarginPx;
- if (!launcher.getDeviceProfile().isVerticalBarLayout()) {
- mContainerPaddingTop = mContainerPaddingBottom = grid.edgeMarginPx;
- } else {
- mContainerPaddingTop = mContainerPaddingBottom = 0;
- }
+ /**
+ * Handles the touch events that shows the workspace when clicking outside the bounds of the
+ * touch delegate target view.
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ // Check if the touch is outside touch delegate target view
+ View touchDelegateTargetView = getTouchDelegateTargetView();
+ float leftBoundPx = touchDelegateTargetView.getLeft();
+ if (ev.getX() < leftBoundPx ||
+ ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) {
+ mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY());
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mLastTouchDownPosPx.x > -1) {
+ ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
+ float dx = ev.getX() - mLastTouchDownPosPx.x;
+ float dy = ev.getY() - mLastTouchDownPosPx.y;
+ float distance = PointF.length(dx, dy);
+ if (distance < viewConfig.getScaledTouchSlop()) {
+ // The background was clicked, so just go home
+ Launcher.getLauncher(getContext()).showWorkspace(true);
+ return true;
+ }
+ }
+ // Fall through
+ case MotionEvent.ACTION_CANCEL:
+ mLastTouchDownPosPx.set(-1, -1);
+ break;
}
-
- mRevealDrawable = new InsetDrawable(mBaseDrawable,
- mContainerPaddingLeft, mContainerPaddingTop, mContainerPaddingRight,
- mContainerPaddingBottom);
- mRevealView.setBackground(mRevealDrawable);
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) {
- // Skip updating the content background
- } else {
- mContent.setBackground(mRevealDrawable);
- }
+ return false;
}
+
+ public abstract View getTouchDelegateTargetView();
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 45bc940..6fdf454 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -18,10 +18,11 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.ViewGroup;
+
import com.android.launcher3.util.Thunk;
@@ -41,12 +42,11 @@
@Thunk int mDy = 0;
private float mDeltaThreshold;
- protected BaseRecyclerViewFastScrollBar mScrollbar;
+ protected final BaseRecyclerViewFastScrollBar mScrollbar;
private int mDownX;
private int mDownY;
private int mLastY;
- protected Rect mBackgroundPadding = new Rect();
public BaseRecyclerView(Context context) {
this(context, null);
@@ -92,6 +92,12 @@
addOnItemTouchListener(this);
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
+ }
+
/**
* 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.
@@ -156,28 +162,11 @@
return false;
}
- public void updateBackgroundPadding(Rect padding) {
- mBackgroundPadding.set(padding);
- }
-
- public Rect getBackgroundPadding() {
- return mBackgroundPadding;
- }
-
/**
- * Returns the scroll bar width when the user is scrolling.
+ * Returns the height of the fast scroll bar
*/
- public int getMaxScrollbarWidth() {
- return mScrollbar.getThumbMaxWidth();
- }
-
- /**
- * Returns the visible height of the recycler view:
- * VisibleHeight = View height - top padding - bottom padding
- */
- protected int getVisibleHeight() {
- int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- return visibleHeight;
+ protected int getScrollbarTrackHeight() {
+ return getHeight();
}
/**
@@ -191,7 +180,7 @@
* AvailableScrollBarHeight = Total height of the visible view - thumb height
*/
protected int getAvailableScrollBarHeight() {
- int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
+ int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
return availableScrollBarHeight;
}
@@ -203,13 +192,6 @@
}
/**
- * Returns the inactive thumb color, can be overridden by each subclass.
- */
- public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
- return defaultInactiveThumbColor;
- }
-
- /**
* Returns the scrollbar for this recycler view.
*/
public BaseRecyclerViewFastScrollBar getScrollBar() {
@@ -233,31 +215,19 @@
protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
int availableScrollHeight) {
// Only show the scrollbar if there is height to be scrolled
- int availableScrollBarHeight = getAvailableScrollBarHeight();
if (availableScrollHeight <= 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
- int scrollBarY = mBackgroundPadding.top +
- (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+ int scrollBarY =
+ (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight());
// Calculate the position and size of the scroll bar
- mScrollbar.setThumbOffset(getScrollBarX(), scrollBarY);
- }
-
- /**
- * @return the x position for the scrollbar thumb
- */
- protected int getScrollBarX() {
- if (Utilities.isRtl(getResources())) {
- return mBackgroundPadding.left;
- } else {
- return getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
- }
+ mScrollbar.setThumbOffsetY(scrollBarY);
}
/**
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 3d71632..40c5ed6 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -15,21 +15,18 @@
*/
package com.android.launcher3;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
-import android.graphics.Point;
import android.graphics.Rect;
+import android.util.Property;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
-
-import com.android.launcher3.util.Thunk;
+import android.widget.TextView;
/**
* The track and scrollbar that shows when you scroll the list.
@@ -40,29 +37,46 @@
void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
}
+ private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
+ new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
+
+ @Override
+ public Integer get(BaseRecyclerViewFastScrollBar scrollBar) {
+ return scrollBar.mWidth;
+ }
+
+ @Override
+ public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) {
+ scrollBar.setTrackWidth(value);
+ }
+ };
+
private final static int MAX_TRACK_ALPHA = 30;
private final static int SCROLL_BAR_VIS_DURATION = 150;
+ private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
- @Thunk BaseRecyclerView mRv;
- private BaseRecyclerViewFastScrollPopup mPopup;
+ private final Rect mTmpRect = new Rect();
+ private final BaseRecyclerView mRv;
- private AnimatorSet mScrollbarAnimator;
+ private final boolean mIsRtl;
- private int mThumbInactiveColor;
- private int mThumbActiveColor;
- @Thunk Point mThumbOffset = new Point(-1, -1);
- @Thunk Paint mThumbPaint;
- private int mThumbMinWidth;
- private int mThumbMaxWidth;
- @Thunk int mThumbWidth;
- @Thunk int mThumbHeight;
- private int mThumbCurvature;
- private Path mThumbPath = new Path();
- private Paint mTrackPaint;
- private int mTrackWidth;
- private float mLastTouchY;
// The inset is the buffer around which a point will still register as a click on the scrollbar
- private int mTouchInset;
+ private final int mTouchInset;
+
+ private final int mMinWidth;
+ private final int mMaxWidth;
+
+ // Current width of the track
+ private int mWidth;
+ private ObjectAnimator mWidthAnimator;
+
+ private final Path mThumbPath = new Path();
+ private final Paint mThumbPaint;
+ private final int mThumbHeight;
+
+ private final Paint mTrackPaint;
+
+ private float mLastTouchY;
private boolean mIsDragging;
private boolean mIsThumbDetached;
private boolean mCanThumbDetach;
@@ -70,27 +84,35 @@
// This is the offset from the top of the scrollbar when the user first starts touching. To
// prevent jumping, this offset is applied as the user scrolls.
- private int mTouchOffset;
+ private int mTouchOffsetY;
+ private int mThumbOffsetY;
- private Rect mInvalidateRect = new Rect();
- private Rect mTmpRect = new Rect();
+ // Fast scroller popup
+ private TextView mPopupView;
+ private boolean mPopupVisible;
+ private String mPopupSectionName;
public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
mRv = rv;
- mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
mTrackPaint = new Paint();
mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
- mThumbActiveColor = mThumbInactiveColor = Utilities.getColorAccent(rv.getContext());
+
mThumbPaint = new Paint();
mThumbPaint.setAntiAlias(true);
- mThumbPaint.setColor(mThumbInactiveColor);
+ mThumbPaint.setColor(Utilities.getColorAccent(rv.getContext()));
mThumbPaint.setStyle(Paint.Style.FILL);
- mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
- mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
+
+ mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
+ mMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
- mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
+ mIsRtl = Utilities.isRtl(res);
+ updateThumbPath();
+ }
+
+ public void setPopupView(View popup) {
+ mPopupView = (TextView) popup;
}
public void setDetachThumbOnFastScroll() {
@@ -101,61 +123,60 @@
mIsThumbDetached = false;
}
- public void setThumbOffset(int x, int y) {
- if (mThumbOffset.x == x && mThumbOffset.y == y) {
+ private int getDrawLeft() {
+ return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth);
+ }
+
+ public void setThumbOffsetY(int y) {
+ if (mThumbOffsetY == y) {
return;
}
- mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mThumbOffset.set(x, y);
+
+ // Invalidate the previous and new thumb area
+ int drawLeft = getDrawLeft();
+ mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+ mThumbOffsetY = y;
+ mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+ mRv.invalidate(mTmpRect);
+ }
+
+ public int getThumbOffsetY() {
+ return mThumbOffsetY;
+ }
+
+ private void setTrackWidth(int width) {
+ if (mWidth == width) {
+ return;
+ }
+ int left = getDrawLeft();
+ // Invalidate the whole scroll bar area.
+ mRv.invalidate(left, 0, left + mMaxWidth, mRv.getScrollbarTrackHeight());
+
+ mWidth = width;
updateThumbPath();
- mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mRv.invalidate(mInvalidateRect);
}
- public Point getThumbOffset() {
- return mThumbOffset;
- }
+ /**
+ * Updates the path for the thumb drawable.
+ */
+ private void updateThumbPath() {
+ int smallWidth = mIsRtl ? mWidth : -mWidth;
+ int largeWidth = mIsRtl ? mMaxWidth : -mMaxWidth;
- // Setter/getter for the thumb bar width for animations
- public void setThumbWidth(int width) {
- mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mThumbWidth = width;
- updateThumbPath();
- mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
- mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
- mRv.invalidate(mInvalidateRect);
- }
-
- public int getThumbWidth() {
- return mThumbWidth;
- }
-
- // Setter/getter for the track bar width for animations
- public void setTrackWidth(int width) {
- mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
- mRv.getVisibleHeight());
- mTrackWidth = width;
- updateThumbPath();
- mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
- mRv.getVisibleHeight());
- mRv.invalidate(mInvalidateRect);
- }
-
- public int getTrackWidth() {
- return mTrackWidth;
+ mThumbPath.reset();
+ mThumbPath.moveTo(0, 0);
+ mThumbPath.lineTo(0, mThumbHeight); // Left edge
+ mThumbPath.lineTo(smallWidth, mThumbHeight); // bottom edge
+ mThumbPath.cubicTo(smallWidth, mThumbHeight, // right edge
+ largeWidth, mThumbHeight / 2,
+ smallWidth, 0);
+ mThumbPath.close();
}
public int getThumbHeight() {
return mThumbHeight;
}
- public int getThumbMaxWidth() {
- return mThumbMaxWidth;
- }
-
public boolean isDraggingThumb() {
return mIsDragging;
}
@@ -176,7 +197,7 @@
switch (action) {
case MotionEvent.ACTION_DOWN:
if (isNearThumb(downX, downY)) {
- mTouchOffset = downY - mThumbOffset.y;
+ mTouchOffsetY = downY - mThumbOffsetY;
}
break;
case MotionEvent.ACTION_MOVE:
@@ -191,32 +212,33 @@
if (mCanThumbDetach) {
mIsThumbDetached = true;
}
- mTouchOffset += (lastY - downY);
- mPopup.animateVisibility(true);
+ mTouchOffsetY += (lastY - downY);
+ animatePopupVisibility(true);
showActiveScrollbar(true);
}
if (mIsDragging) {
// Update the fastscroller section name at this touch position
- int top = mRv.getBackgroundPadding().top;
- int bottom = top + mRv.getVisibleHeight() - mThumbHeight;
- float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
- String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
- (bottom - top));
- mPopup.setSectionName(sectionName);
- mPopup.animateVisibility(!sectionName.isEmpty());
- mRv.invalidate(mPopup.updateFastScrollerBounds(lastY));
+ int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
+ float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
+ String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+ if (!sectionName.equals(mPopupSectionName)) {
+ mPopupSectionName = sectionName;
+ mPopupView.setText(sectionName);
+ }
+ animatePopupVisibility(!sectionName.isEmpty());
+ updatePopupY(lastY);
mLastTouchY = boundedY;
- setThumbOffset(mRv.getScrollBarX(), (int) mLastTouchY);
+ setThumbOffsetY((int) mLastTouchY);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- mTouchOffset = 0;
+ mTouchOffsetY = 0;
mLastTouchY = 0;
mIgnoreDragGesture = false;
if (mIsDragging) {
mIsDragging = false;
- mPopup.animateVisibility(false);
+ animatePopupVisibility(false);
showActiveScrollbar(false);
}
break;
@@ -224,74 +246,58 @@
}
public void draw(Canvas canvas) {
- if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
+ if (mThumbOffsetY < 0) {
return;
}
-
- // Draw the scroll bar track and thumb
- if (mTrackPaint.getAlpha() > 0) {
- canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
- mRv.getVisibleHeight(), mTrackPaint);
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ if (!mIsRtl) {
+ canvas.translate(mRv.getWidth(), 0);
}
- canvas.drawPath(mThumbPath, mThumbPaint);
+ // Draw the track
+ int thumbWidth = mIsRtl ? mWidth : -mWidth;
+ canvas.drawRect(0, 0, thumbWidth, mRv.getScrollbarTrackHeight(), mTrackPaint);
- // Draw the popup
- mPopup.draw(canvas);
+ canvas.translate(0, mThumbOffsetY);
+ canvas.drawPath(mThumbPath, mThumbPaint);
+ canvas.restoreToCount(saveCount);
}
/**
- * Animates the width and color of the scrollbar.
+ * Animates the width of the scrollbar.
*/
private void showActiveScrollbar(boolean isScrolling) {
- if (mScrollbarAnimator != null) {
- mScrollbarAnimator.cancel();
+ if (mWidthAnimator != null) {
+ mWidthAnimator.cancel();
}
- mScrollbarAnimator = new AnimatorSet();
- ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
- isScrolling ? mThumbMaxWidth : mThumbMinWidth);
- ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
- isScrolling ? mThumbMaxWidth : mThumbMinWidth);
- mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
- if (mThumbActiveColor != mThumbInactiveColor) {
- ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
- mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
- colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- mThumbPaint.setColor((Integer) animator.getAnimatedValue());
- mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight);
- }
- });
- mScrollbarAnimator.play(colorAnimation);
- }
- mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
- mScrollbarAnimator.start();
- }
-
- /**
- * Updates the path for the thumb drawable.
- */
- private void updateThumbPath() {
- mThumbCurvature = mThumbMaxWidth - mThumbWidth;
- mThumbPath.reset();
- mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y); // tr
- mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight); // br
- mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight); // bl
- mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
- mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
- mThumbOffset.x, mThumbOffset.y); // bl2tl
- mThumbPath.close();
+ mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
+ isScrolling ? mMaxWidth : mMinWidth);
+ mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
+ mWidthAnimator.start();
}
/**
* Returns whether the specified points are near the scroll bar bounds.
*/
public boolean isNearThumb(int x, int y) {
- mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
- mThumbOffset.y + mThumbHeight);
+ int left = getDrawLeft();
+ mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight);
mTmpRect.inset(mTouchInset, mTouchInset);
return mTmpRect.contains(x, y);
}
+
+ private void animatePopupVisibility(boolean visible) {
+ if (mPopupVisible != visible) {
+ mPopupVisible = visible;
+ mPopupView.animate().cancel();
+ mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
+ }
+ }
+
+ private void updatePopupY(int lastTouchY) {
+ int height = mPopupView.getHeight();
+ float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height);
+ top = Math.max(mMaxWidth, Math.min(top, mRv.getScrollbarTrackHeight() - mMaxWidth - height));
+ mPopupView.setTranslationY(top);
+ }
}
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
deleted file mode 100644
index b9e6277..0000000
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-/**
- * The fast scroller popup that shows the section name the list will jump to.
- */
-public class BaseRecyclerViewFastScrollPopup {
-
- private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
-
- private static final int SHADOW_INSET = 3;
- private static final int SHADOW_SHIFT_Y = 2;
- private static final float SHADOW_ALPHA_MULTIPLIER = 0.67f;
-
- private Resources mRes;
- private BaseRecyclerView mRv;
-
- private Bitmap mShadow;
- private Paint mShadowPaint;
-
- private Drawable mBg;
- // The absolute bounds of the fast scroller bg
- private Rect mBgBounds = new Rect();
- private int mBgOriginalSize;
- private Rect mInvalidateRect = new Rect();
- private Rect mTmpRect = new Rect();
-
- private String mSectionName;
- private Paint mTextPaint;
- private Rect mTextBounds = new Rect();
- private float mAlpha;
-
- private Animator mAlphaAnimator;
- private boolean mVisible;
-
- public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) {
- mRes = res;
- mRv = rv;
-
- mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size);
- mBg = rv.getContext().getDrawable(R.drawable.container_fastscroll_popup_bg);
- mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize);
-
- mTextPaint = new Paint();
- mTextPaint.setColor(Color.WHITE);
- mTextPaint.setAntiAlias(true);
- mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size));
-
- mShadowPaint = new Paint();
- mShadowPaint.setAntiAlias(true);
- mShadowPaint.setFilterBitmap(true);
- mShadowPaint.setDither(true);
- }
-
- /**
- * Sets the section name.
- */
- public void setSectionName(String sectionName) {
- if (!sectionName.equals(mSectionName)) {
- mSectionName = sectionName;
- mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds);
- // Update the width to use measureText since that is more accurate
- mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName));
- }
- }
-
- /**
- * Updates the bounds for the fast scroller.
- *
- * @return the invalidation rect for this update.
- */
- public Rect updateFastScrollerBounds(int lastTouchY) {
- mInvalidateRect.set(mBgBounds);
-
- if (isVisible()) {
- // Calculate the dimensions and position of the fast scroller popup
- int edgePadding = mRv.getMaxScrollbarWidth();
- int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
- int bgHeight = mBgOriginalSize;
- int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
- if (Utilities.isRtl(mRes)) {
- mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth());
- mBgBounds.right = mBgBounds.left + bgWidth;
- } else {
- mBgBounds.right = mRv.getWidth() - mRv.getBackgroundPadding().right -
- (2 * mRv.getMaxScrollbarWidth());
- mBgBounds.left = mBgBounds.right - bgWidth;
- }
- mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
- mBgBounds.top = Math.max(edgePadding,
- Math.min(mBgBounds.top, mRv.getVisibleHeight() - edgePadding - bgHeight));
- mBgBounds.bottom = mBgBounds.top + bgHeight;
-
- // Generate a bitmap for a shadow matching these bounds
- mShadow = HolographicOutlineHelper.obtain(
- mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */);
- } else {
- mShadow = null;
- mBgBounds.setEmpty();
- }
-
- // Combine the old and new fast scroller bounds to create the full invalidate rect
- mInvalidateRect.union(mBgBounds);
- return mInvalidateRect;
- }
-
- /**
- * Animates the visibility of the fast scroller popup.
- */
- public void animateVisibility(boolean visible) {
- if (mVisible != visible) {
- mVisible = visible;
- if (mAlphaAnimator != null) {
- mAlphaAnimator.cancel();
- }
- mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f);
- mAlphaAnimator.setDuration(visible ? 200 : 150);
- mAlphaAnimator.start();
- }
- }
-
- // Setter/getter for the popup alpha for animations
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- mRv.invalidate(mBgBounds);
- }
-
- public float getAlpha() {
- return mAlpha;
- }
-
- public int getHeight() {
- return mBgOriginalSize;
- }
-
- public void draw(Canvas c) {
- if (isVisible()) {
- // Determine the alpha and prepare the canvas
- final int alpha = (int) (mAlpha * 255);
- int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG);
- c.translate(mBgBounds.left, mBgBounds.top);
- mTmpRect.set(mBgBounds);
- mTmpRect.offsetTo(0, 0);
-
- // Expand the rect (with a negative inset), translate it, and draw the shadow
- if (mShadow != null) {
- mTmpRect.inset(-SHADOW_INSET * 2, -SHADOW_INSET * 2);
- mTmpRect.offset(0, SHADOW_SHIFT_Y);
- mShadowPaint.setAlpha((int) (alpha * SHADOW_ALPHA_MULTIPLIER));
- c.drawBitmap(mShadow, null, mTmpRect, mShadowPaint);
- mTmpRect.inset(SHADOW_INSET * 2, SHADOW_INSET * 2);
- mTmpRect.offset(0, -SHADOW_SHIFT_Y);
- }
-
- // Draw the background
- mBg.setBounds(mTmpRect);
- mBg.setAlpha(alpha);
- mBg.draw(c);
-
- // Draw the text
- mTextPaint.setAlpha(alpha);
- c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
- mBgBounds.height() - (mBgBounds.height() / 2) - mTextBounds.exactCenterY(),
- mTextPaint);
- c.restoreToCount(restoreCount);
- }
- }
-
- public boolean isVisible() {
- return (mAlpha > 0f) && (mSectionName != null);
- }
-}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a294fa5..dbb797d 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -42,6 +42,7 @@
import com.android.launcher3.IconCache.IconLoadRequest;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
import com.android.launcher3.model.PackageItemInfo;
import java.text.NumberFormat;
@@ -127,7 +128,9 @@
setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
defaultIconSize = grid.allAppsIconSizePx;
} else if (display == DISPLAY_FOLDER) {
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
+ defaultIconSize = grid.folderChildIconSizePx;
}
mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -150,7 +153,7 @@
mLongPressHelper = new CheckLongPressHelper(this);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
- mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+ mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
}
@@ -188,9 +191,7 @@
private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon);
- if (info.isDisabled()) {
- iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
- }
+ iconDrawable.setIsDisabled(info.isDisabled());
setIcon(iconDrawable);
setText(info.title);
if (info.contentDescription != null) {
@@ -259,10 +260,7 @@
private void updateIconState() {
if (mIcon instanceof FastBitmapDrawable) {
FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
- if (getTag() instanceof ItemInfo
- && ((ItemInfo) getTag()).isDisabled()) {
- d.animateState(FastBitmapDrawable.State.DISABLED);
- } else if (isPressed() || mStayPressed) {
+ if (isPressed() || mStayPressed) {
d.animateState(FastBitmapDrawable.State.PRESSED);
} else {
d.animateState(FastBitmapDrawable.State.NORMAL);
@@ -328,7 +326,7 @@
void setStayPressed(boolean stayPressed) {
mStayPressed = stayPressed;
if (!stayPressed) {
- HolographicOutlineHelper.obtain(getContext()).recycleShadowBitmap(mPressedBackground);
+ HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground);
mPressedBackground = null;
} else {
if (mPressedBackground == null) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 57fd0e7..3564cec 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -87,13 +87,6 @@
@ViewDebug.ExportedProperty(category = "launcher")
private int mCountY;
- private int mOriginalWidthGap;
- private int mOriginalHeightGap;
- @ViewDebug.ExportedProperty(category = "launcher")
- @Thunk int mWidthGap;
- @ViewDebug.ExportedProperty(category = "launcher")
- @Thunk int mHeightGap;
- private int mMaxGap;
private boolean mDropPending = false;
private boolean mIsDragTarget = true;
private boolean mJailContent = true;
@@ -202,9 +195,6 @@
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
- mWidthGap = mOriginalWidthGap = 0;
- mHeightGap = mOriginalHeightGap = 0;
- mMaxGap = Integer.MAX_VALUE;
mCountX = grid.inv.numColumns;
mCountY = grid.inv.numRows;
@@ -287,8 +277,7 @@
}
mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
@@ -373,8 +362,7 @@
public void setCellDimensions(int width, int height) {
mFixedCellWidth = mCellWidth = width;
mFixedCellHeight = mCellHeight = height;
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
}
public void setGridSize(int x, int y) {
@@ -383,8 +371,7 @@
mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mTempRectStack.clear();
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
requestLayout();
}
@@ -618,7 +605,9 @@
public void setIsHotseat(boolean isHotseat) {
mIsHotseat = isHotseat;
- mShortcutsAndWidgets.setIsHotseat(isHotseat);
+ mShortcutsAndWidgets.setContainerType(isHotseat
+ ? ShortcutAndWidgetContainer.HOTSEAT
+ : ShortcutAndWidgetContainer.DEFAULT);
}
public boolean isHotseat() {
@@ -717,8 +706,8 @@
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
- result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
+ result[0] = (x - hStartPadding) / mCellWidth;
+ result[1] = (y - vStartPadding) / mCellHeight;
final int xAxis = mCountX;
final int yAxis = mCountY;
@@ -751,8 +740,8 @@
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
+ result[0] = hStartPadding + cellX * mCellWidth;
+ result[1] = vStartPadding + cellY * mCellHeight;
}
/**
@@ -778,10 +767,8 @@
void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
- (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
- (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
+ result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
+ result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
}
/**
@@ -794,10 +781,9 @@
void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
- final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
- result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
- top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
+ final int left = hStartPadding + cellX * mCellWidth;
+ final int top = vStartPadding + cellY * mCellHeight;
+ result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
}
public float getDistanceFromCell(float x, float y, int[] cell) {
@@ -813,14 +799,6 @@
return mCellHeight;
}
- int getWidthGap() {
- return mWidthGap;
- }
-
- int getHeightGap() {
- return mHeightGap;
- }
-
public void setFixedSize(int width, int height) {
mFixedWidth = width;
mFixedHeight = height;
@@ -840,8 +818,7 @@
if (cw != mCellWidth || ch != mCellHeight) {
mCellWidth = cw;
mCellHeight = ch;
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
- mHeightGap, mCountX, mCountY);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
}
}
@@ -854,23 +831,6 @@
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
}
- int numWidthGaps = mCountX - 1;
- int numHeightGaps = mCountY - 1;
-
- if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
- int hSpace = childWidthSize;
- int vSpace = childHeightSize;
- int hFreeSpace = hSpace - (mCountX * mCellWidth);
- int vFreeSpace = vSpace - (mCountY * mCellHeight);
- mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
- mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
- mHeightGap, mCountX, mCountY);
- } else {
- mWidthGap = mOriginalWidthGap;
- mHeightGap = mOriginalHeightGap;
- }
-
// Make the feedback view large enough to hold the blur bitmap.
mTouchFeedbackView.measure(
MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
@@ -930,16 +890,6 @@
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
}
- @Override
- protected void setChildrenDrawingCacheEnabled(boolean enabled) {
- mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
- }
-
- @Override
- protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
- mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
- }
-
public float getBackgroundAlpha() {
return mBackgroundAlpha;
}
@@ -1054,11 +1004,11 @@
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
- if (outlineProvider == null || outlineProvider.gerenatedDragOutline == null) {
+ if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
return;
}
- Bitmap dragOutline = outlineProvider.gerenatedDragOutline;
+ Bitmap dragOutline = outlineProvider.generatedDragOutline;
if (cellX != oldDragCellX || cellY != oldDragCellY) {
Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
Rect dragRegion = dragObject.dragView.getDragRegion();
@@ -1093,23 +1043,19 @@
// outside the bounds of the view.
top += (v.getHeight() - dragOutline.getHeight()) / 2;
// We center about the x axis
- left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragOutline.getWidth()) / 2;
+ left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
} else {
if (dragOffset != null && dragRegion != null) {
// Center the drag region *horizontally* in the cell and apply a drag
// outline offset
- left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragRegion.width()) / 2;
+ left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
int cHeight = getShortcutsAndWidgets().getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
top += dragOffset.y + cellPaddingY;
} else {
// Center the drag outline in the cell
- left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragOutline.getWidth()) / 2;
- top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
- - dragOutline.getHeight()) / 2;
+ left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+ top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
}
}
r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
@@ -1198,8 +1144,8 @@
// For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
// to the center of the item, but we are searching based on the top-left cell, so
// we translate the point over to correspond to the top-left.
- pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
- pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
+ pixelX -= mCellWidth * (spanX - 1) / 2f;
+ pixelY -= mCellHeight * (spanY - 1) / 2f;
// Keep track of best-scoring drop area
final int[] bestXY = result != null ? result : new int[2];
@@ -2215,10 +2161,6 @@
return solution;
}
- public void prepareChildForDrag(View child) {
- markCellsAsUnoccupiedForView(child);
- }
-
/* This seems like it should be obvious and straight-forward, but when the direction vector
needs to match with the notion of the dragView pushing other views, we have to employ
a slightly more subtle notion of the direction vector. The question is what two points is
@@ -2598,17 +2540,14 @@
public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
final int cellWidth = mCellWidth;
final int cellHeight = mCellHeight;
- final int widthGap = mWidthGap;
- final int heightGap = mHeightGap;
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
- int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
- int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
-
- int x = hStartPadding + cellX * (cellWidth + widthGap);
- int y = vStartPadding + cellY * (cellHeight + heightGap);
+ int width = cellHSpan * cellWidth;
+ int height = cellVSpan * cellHeight;
+ int x = hStartPadding + cellX * cellWidth;
+ int y = vStartPadding + cellY * cellHeight;
resultRect.set(x, y, x + width, y + height);
}
@@ -2626,13 +2565,11 @@
}
public int getDesiredWidth() {
- return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
- (Math.max((mCountX - 1), 0) * mWidthGap);
+ return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
}
public int getDesiredHeight() {
- return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
- (Math.max((mCountY - 1), 0) * mHeightGap);
+ return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
}
public boolean isOccupied(int x, int y) {
@@ -2752,8 +2689,7 @@
this.cellVSpan = cellVSpan;
}
- public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
- boolean invertHorizontally, int colCount) {
+ public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
@@ -2764,12 +2700,10 @@
myCellX = colCount - myCellX - cellHSpan;
}
- width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
- leftMargin - rightMargin;
- height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
- topMargin - bottomMargin;
- x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
- y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
+ width = myCellHSpan * cellWidth - leftMargin - rightMargin;
+ height = myCellVSpan * cellHeight - topMargin - bottomMargin;
+ x = (myCellX * cellWidth + leftMargin);
+ y = (myCellY * cellHeight + topMargin);
}
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f9f8e80..59ec56a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -96,16 +96,23 @@
public int folderBackgroundOffset;
public int folderIconSizePx;
public int folderIconPreviewPadding;
+
+ // Folder cell
public int folderCellWidthPx;
public int folderCellHeightPx;
+
+ // Folder child
+ public int folderChildIconSizePx;
+ public int folderChildTextSizePx;
public int folderChildDrawablePaddingPx;
// Hotseat
public int hotseatCellWidthPx;
public int hotseatCellHeightPx;
public int hotseatIconSizePx;
- private int hotseatBarHeightPx;
+ public int hotseatBarHeightPx;
private int hotseatBarTopPaddingPx;
+ private int hotseatBarBottomPaddingPx;
private int hotseatLandGutterPx;
// All apps
@@ -116,10 +123,6 @@
public int allAppsIconDrawablePaddingPx;
public float allAppsIconTextSizePx;
- // Containers
- private final int containerLeftPaddingPx;
- private final int containerRightPaddingPx;
-
// Drop Target
public int dropTargetBarSizePx;
@@ -183,11 +186,8 @@
hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height);
hotseatBarTopPaddingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
+ hotseatBarBottomPaddingPx = 0;
hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
- containerLeftPaddingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_container_land_left_padding);
- containerRightPaddingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_container_land_right_padding);
// Determine sizes.
widthPx = width;
@@ -205,6 +205,24 @@
computeAllAppsButtonSize(context);
}
+ DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
+ // In multi-window mode, we can have widthPx = availableWidthPx
+ // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
+ // widthPx and heightPx values where it's needed.
+ DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
+ isLandscape);
+
+ // Hide labels on the workspace.
+ profile.iconTextSizePx = 0;
+ profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx
+ + Utilities.calculateTextHeight(profile.iconTextSizePx);
+
+ // The nav bar is black so we add bottom padding to visually center hotseat icons.
+ profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx;
+
+ return profile;
+ }
+
public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
if (!mListeners.contains(listener)) {
mListeners.add(listener);
@@ -229,19 +247,17 @@
}
private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
- // Check to see if the icons fit in the new available height. If not, then we need to
- // shrink the icon size.
- float scale = 1f;
- int drawablePadding = iconDrawablePaddingOriginalPx;
- updateIconSize(1f, drawablePadding, res, dm);
- float usedHeight = (cellHeightPx * inv.numRows);
+ updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm);
+ // Check to see if the icons fit within the available height. If not, then scale down.
+ float usedHeight = (cellHeightPx * inv.numRows);
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
if (usedHeight > maxHeight) {
- scale = maxHeight / usedHeight;
- drawablePadding = 0;
+ float scale = maxHeight / usedHeight;
+ updateIconSize(scale, 0, res, dm);
}
- updateIconSize(scale, drawablePadding, res, dm);
+
+ updateAvailableFolderCellDimensions(dm, res);
}
private void updateIconSize(float scale, int drawablePadding, Resources res,
@@ -277,31 +293,47 @@
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
}
- // Folder cell
- int cellPaddingX = res.getDimensionPixelSize(R.dimen.folder_cell_x_padding);
- int cellPaddingY = res.getDimensionPixelSize(R.dimen.folder_cell_y_padding);
- final int folderChildTextSize =
- Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_child_text_size));
-
- final int folderBottomPanelSize =
- res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
- + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
- + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
-
- // Don't let the folder get too close to the edges of the screen.
- folderCellWidthPx = Math.min(iconSizePx + 2 * cellPaddingX,
- (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
- folderCellHeightPx = Math.min(iconSizePx + 3 * cellPaddingY + folderChildTextSize,
- (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows);
- folderChildDrawablePaddingPx = Math.max(0,
- (folderCellHeightPx - iconSizePx - folderChildTextSize) / 3);
-
// Folder icon
folderBackgroundOffset = -edgeMarginPx;
folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
}
+ private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
+ int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
+ + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
+ + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
+
+ updateFolderCellSize(1f, dm, res, folderBottomPanelSize);
+
+ // Check to see if the icons fit within the available height. If not, then scale down.
+ float usedHeight = (folderCellHeightPx * inv.numFolderRows) + folderBottomPanelSize;
+ int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - (2 * edgeMarginPx);
+ if (usedHeight > maxHeight) {
+ float scale = maxHeight / usedHeight;
+ updateFolderCellSize(scale, dm, res, folderBottomPanelSize);
+ }
+ }
+
+ private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res,
+ int folderBottomPanelSize) {
+ folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
+ folderChildTextSizePx =
+ (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
+
+ int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
+ int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
+ int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
+
+ // Don't let the folder get too close to the edges of the screen.
+ folderCellWidthPx = Math.min(folderChildIconSizePx + 2 * cellPaddingX,
+ (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
+ folderCellHeightPx = Math.min(folderChildIconSizePx + 2 * cellPaddingY + textHeight,
+ (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows);
+ folderChildDrawablePaddingPx = Math.max(0,
+ (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
+ }
+
public void updateInsets(Rect insets) {
mInsets.set(insets);
}
@@ -377,7 +409,8 @@
availablePaddingX = (int) Math.min(availablePaddingX,
width * MAX_HORIZONTAL_PADDING_PERCENT);
int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
- - (int) (2 * inv.numRows * cellHeightPx));
+ - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
+ - hotseatBarBottomPaddingPx);
padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
} else {
@@ -463,7 +496,6 @@
public void layout(Launcher launcher, boolean notifyListeners) {
FrameLayout.LayoutParams lp;
boolean hasVerticalBarLayout = isVerticalBarLayout();
- final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
// Layout the search bar space
Point searchBarBounds = getSearchBarDimensForWidgetOpts();
@@ -511,7 +543,7 @@
lp.height = hotseatBarHeightPx + mInsets.bottom;
hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
- mInsets.bottom);
+ hotseatBarBottomPaddingPx + mInsets.bottom);
} else {
// For phones, layout the hotseat without any bottom margin
// to ensure that we have space for the folders
@@ -520,7 +552,7 @@
lp.height = hotseatBarHeightPx + mInsets.bottom;
hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
- mInsets.bottom);
+ hotseatBarBottomPaddingPx + mInsets.bottom);
}
hotseat.setLayoutParams(lp);
@@ -549,18 +581,13 @@
// Layout the Overview Mode
ViewGroup overviewMode = launcher.getOverviewPanel();
if (overviewMode != null) {
- lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
- lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
-
int visibleChildCount = getVisibleChildCount(overviewMode);
int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
- int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
+ int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
+ lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
lp.width = Math.min(availableWidthPx, maxWidth);
- lp.height = getOverviewModeButtonBarHeight();
- // Center the overview buttons on the workspace page
- lp.leftMargin = workspacePadding.left + (availableWidthPx -
- workspacePadding.left - workspacePadding.right - lp.width) / 2;
+ lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom;
overviewMode.setLayoutParams(lp);
}
@@ -587,9 +614,7 @@
/**
* @return the left/right paddings for all containers.
*/
- public final int[] getContainerPadding(Context context) {
- Resources res = context.getResources();
-
+ public final int[] getContainerPadding() {
// No paddings for portrait phone
if (isPhone && !isVerticalBarLayout()) {
return new int[] {0, 0};
@@ -600,4 +625,11 @@
hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2;
return new int[]{ padding, padding };
}
+
+ public boolean shouldIgnoreLongPressToOverview(float touchX) {
+ boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile;
+ boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
+ boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
+ return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
+ }
}
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index efbb9d7..2fb495f 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -19,12 +19,12 @@
import android.view.View;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.logging.UserEventDispatcher.LaunchSourceProvider;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
/**
* Interface defining an object that can originate a drag.
*/
-public interface DragSource extends LaunchSourceProvider {
+public interface DragSource extends LogContainerProvider {
/**
* @return whether items dragged from this source supports
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index efdeb1f..e91fc15 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -107,17 +107,6 @@
/**
* Handle an object being dropped on the DropTarget
- *
- * @param source DragSource where the drag started
- * @param x X coordinate of the drop location
- * @param y Y coordinate of the drop location
- * @param xOffset Horizontal offset with the object being dragged where the original
- * touch happened
- * @param yOffset Vertical offset with the object being dragged where the original
- * touch happened
- * @param dragView The DragView that's being dragged around on screen.
- * @param dragInfo Data associated with the object being dragged
- *
*/
void onDrop(DragObject dragObject);
@@ -137,16 +126,6 @@
/**
* Check if a drop action can occur at, or near, the requested location.
* This will be called just before onDrop.
- *
- * @param source DragSource where the drag started
- * @param x X coordinate of the drop location
- * @param y Y coordinate of the drop location
- * @param xOffset Horizontal offset with the object being dragged where the
- * original touch happened
- * @param yOffset Vertical offset with the object being dragged where the
- * original touch happened
- * @param dragView The DragView that's being dragged around on screen.
- * @param dragInfo Data associated with the object being dragged
* @return True if the drop will be accepted, false otherwise.
*/
boolean acceptDrop(DragObject dragObject);
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index c06f727..d05673c 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -99,4 +99,12 @@
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
}
+
+ public void dispatchBackKey() {
+ ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
+ .hideSoftInputFromWindow(getWindowToken(), 0);
+ if (mBackKeyListener != null) {
+ mBackKeyListener.onBackKey();
+ }
+ }
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 3870080..7eaae5a 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -34,6 +34,8 @@
import android.view.animation.DecelerateInterpolator;
public class FastBitmapDrawable extends Drawable {
+ private static final float DISABLED_DESATURATION = 1f;
+ private static final float DISABLED_BRIGHTNESS = 0.5f;
/**
* The possible states that a FastBitmapDrawable can be in.
@@ -43,8 +45,7 @@
NORMAL (0f, 0f, 1f, new DecelerateInterpolator()),
PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.15f, new DecelerateInterpolator()),
- FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator()),
- DISABLED (1f, 0.5f, 1f, new DecelerateInterpolator());
+ FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator());
public final float desaturation;
public final float brightness;
@@ -96,6 +97,7 @@
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
private final Bitmap mBitmap;
private State mState = State.NORMAL;
+ private boolean mIsDisabled;
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
@@ -177,13 +179,14 @@
if (mState != newState) {
mState = newState;
+ float desaturation = mIsDisabled ? DISABLED_DESATURATION : newState.desaturation;
+ float brightness = mIsDisabled ? DISABLED_BRIGHTNESS: newState.brightness;
+
mPropertyAnimator = cancelAnimator(mPropertyAnimator);
mPropertyAnimator = new AnimatorSet();
mPropertyAnimator.playTogether(
- ObjectAnimator
- .ofFloat(this, "desaturation", newState.desaturation),
- ObjectAnimator
- .ofFloat(this, "brightness", newState.brightness));
+ ObjectAnimator.ofFloat(this, "desaturation", desaturation),
+ ObjectAnimator.ofFloat(this, "brightness", brightness));
mPropertyAnimator.setInterpolator(newState.interpolator);
mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
@@ -204,13 +207,17 @@
mPropertyAnimator = cancelAnimator(mPropertyAnimator);
- setDesaturation(newState.desaturation);
- setBrightness(newState.brightness);
+ invalidateDesaturationAndBrightness();
return true;
}
return false;
}
+ private void invalidateDesaturationAndBrightness() {
+ setDesaturation(mIsDisabled ? DISABLED_DESATURATION : mState.desaturation);
+ setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS: mState.brightness);
+ }
+
/**
* Returns the current state.
*/
@@ -218,6 +225,13 @@
return mState;
}
+ public void setIsDisabled(boolean isDisabled) {
+ if (mIsDisabled != isDisabled) {
+ mIsDisabled = isDisabled;
+ invalidateDesaturationAndBrightness();
+ }
+ }
+
/**
* Returns the duration for the state change animation.
*/
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 789c3f9..b36734b 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -250,14 +250,6 @@
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
- } else if (isUninstallKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
- UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
- }
- } else if (isDeleteKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
} else {
// For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
// matrix extended with hotseat.
@@ -374,14 +366,6 @@
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
- } else if (isUninstallKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
- UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
- }
- } else if (isDeleteKeyChord(e)) {
- matrix = FocusLogic.createSparseMatrix(iconLayout);
- launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
} else {
matrix = FocusLogic.createSparseMatrix(iconLayout);
}
@@ -532,24 +516,6 @@
}
}
- /**
- * Returns whether the key event represents a valid uninstall key chord.
- */
- private static boolean isUninstallKeyChord(KeyEvent event) {
- int keyCode = event.getKeyCode();
- return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
- }
-
- /**
- * Returns whether the key event represents a valid delete key chord.
- */
- private static boolean isDeleteKeyChord(KeyEvent event) {
- int keyCode = event.getKeyCode();
- return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON);
- }
-
private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
int pageIndex, boolean isRtl) {
if (pageIndex - 1 < 0) {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index c0a8caa..c244235 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,10 +16,10 @@
package com.android.launcher3;
-import android.content.ContentValues;
import android.content.Context;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
import java.util.ArrayList;
@@ -45,11 +45,6 @@
*/
public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
- /**
- * Whether this folder has been opened
- */
- public boolean opened;
-
public int options;
/**
@@ -98,10 +93,10 @@
}
@Override
- void onAddToDatabase(Context context, ContentValues values) {
- super.onAddToDatabase(context, values);
- values.put(LauncherSettings.Favorites.TITLE, title.toString());
- values.put(LauncherSettings.Favorites.OPTIONS, options);
+ void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites.TITLE, title)
+ .put(LauncherSettings.Favorites.OPTIONS, options);
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 0fbbc19..b93c6df 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -42,7 +42,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
public class Hotseat extends FrameLayout
- implements UserEventDispatcher.LaunchSourceProvider {
+ implements UserEventDispatcher.LogContainerProvider {
private CellLayout mContent;
@@ -172,7 +172,7 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.gridX = info.cellX;
target.gridY = info.cellY;
targetParent.containerType = LauncherLogProto.HOTSEAT;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5c86b6b..db72b2f 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -47,6 +47,7 @@
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SQLiteCacheHelper;
@@ -78,7 +79,7 @@
@Thunk static final Object ICON_UPDATE_TOKEN = new Object();
- @Thunk static class CacheEntry {
+ public static class CacheEntry {
public Bitmap icon;
public CharSequence title = "";
public CharSequence contentDescription = "";
@@ -187,7 +188,7 @@
private Bitmap makeDefaultIcon(UserHandleCompat user) {
Drawable unbadged = getFullResDefaultActivityIcon();
- return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
+ return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext);
}
/**
@@ -223,7 +224,7 @@
PackageManager.GET_UNINSTALLED_PACKAGES);
long userSerial = mUserManager.getSerialNumberForUser(user);
for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
- addIconToDBAndMemCache(app, info, userSerial);
+ addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
}
} catch (NameNotFoundException e) {
Log.d(TAG, "Package not found", e);
@@ -353,29 +354,14 @@
}
}
- @Thunk void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
- long userSerial) {
- // Reuse the existing entry if it already exists in the DB. This ensures that we do not
- // create bitmap if it was already created during loader.
- ContentValues values = updateCacheAndGetContentValues(app, false);
- addIconToDB(values, app.getComponentName(), info, userSerial);
- }
-
/**
- * Updates {@param values} to contain versoning information and adds it to the DB.
- * @param values {@link ContentValues} containing icon & title
+ * Adds an entry into the DB and the in-memory cache.
+ * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
+ * the memory. This is useful then the previous bitmap was created using
+ * old data.
*/
- 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.insertOrReplace(values);
- }
-
- @Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
- boolean replaceExisting) {
+ @Thunk synchronized void addIconToDBAndMemCache(LauncherActivityInfoCompat app,
+ PackageInfo info, long userSerial, boolean replaceExisting) {
final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
CacheEntry entry = null;
if (!replaceExisting) {
@@ -387,17 +373,31 @@
}
if (entry == null) {
entry = new CacheEntry();
- entry.icon = Utilities.createBadgedIconBitmap(
+ entry.icon = LauncherIcons.createBadgedIconBitmap(
mIconProvider.getIcon(app, mIconDpi), app.getUser(),
mContext);
}
entry.title = app.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
- mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
+ mCache.put(key, entry);
Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor);
- return newContentValues(entry.icon, lowResIcon, entry.title.toString(),
+ ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(),
app.getApplicationInfo().packageName);
+ addIconToDB(values, app.getComponentName(), info, userSerial);
+ }
+
+ /**
+ * Updates {@param values} to contain versioning 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.insertOrReplace(values);
}
/**
@@ -494,7 +494,6 @@
shortcutInfo.setIcon(getDefaultIcon(user));
shortcutInfo.title = "";
shortcutInfo.contentDescription = "";
- shortcutInfo.usingFallbackIcon = true;
shortcutInfo.usingLowResIcon = false;
} else {
LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
@@ -512,7 +511,6 @@
shortcutInfo.setIcon(getNonNullIcon(entry, user));
shortcutInfo.title = Utilities.trim(entry.title);
shortcutInfo.contentDescription = entry.contentDescription;
- shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
shortcutInfo.usingLowResIcon = entry.isLowResIcon;
}
@@ -544,7 +542,7 @@
* Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
* This method is not thread safe, it must be called from a synchronized method.
*/
- private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
+ protected CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
@@ -555,7 +553,7 @@
// Check the DB first.
if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
if (info != null) {
- entry.icon = Utilities.createBadgedIconBitmap(
+ entry.icon = LauncherIcons.createBadgedIconBitmap(
mIconProvider.getIcon(info, mIconDpi), info.getUser(),
mContext);
} else {
@@ -606,7 +604,7 @@
entry.title = title;
}
if (icon != null) {
- entry.icon = Utilities.createIconBitmap(icon, mContext);
+ entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
}
}
@@ -641,7 +639,7 @@
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
- Bitmap icon = Utilities.createBadgedIconBitmap(
+ Bitmap icon = LauncherIcons.createBadgedIconBitmap(
appInfo.loadIcon(mPackageManager), user, mContext);
Bitmap lowResIcon = generateLowResIcon(icon, mPackageBgColor);
entry.title = appInfo.loadLabel(mPackageManager);
@@ -774,13 +772,9 @@
LauncherActivityInfoCompat app = mAppsToUpdate.pop();
String pkg = app.getComponentName().getPackageName();
PackageInfo info = mPkgInfoMap.get(pkg);
- if (info != null) {
- synchronized (IconCache.this) {
- ContentValues values = updateCacheAndGetContentValues(app, true);
- addIconToDB(values, app.getComponentName(), info, mUserSerial);
- }
- mUpdatedPackages.add(pkg);
- }
+ addIconToDBAndMemCache(app, info, mUserSerial, true /*replace existing*/);
+ mUpdatedPackages.add(pkg);
+
if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
// No more app to update. Notify model.
LauncherAppState.getInstance().getModel().onPackageIconsUpdated(
@@ -792,10 +786,10 @@
} else if (!mAppsToAdd.isEmpty()) {
LauncherActivityInfoCompat app = mAppsToAdd.pop();
PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
+ // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
+ // app should have package info, this is not guaranteed by the api
if (info != null) {
- synchronized (IconCache.this) {
- addIconToDBAndMemCache(app, info, mUserSerial);
- }
+ addIconToDBAndMemCache(app, info, mUserSerial, false /*replace existing*/);
}
if (!mAppsToAdd.isEmpty()) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d8e58d8..056facb 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -22,7 +22,6 @@
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;
@@ -226,7 +225,8 @@
String packageName = pendingInfo.getTargetPackage();
if (!TextUtils.isEmpty(packageName)) {
UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
- if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
+ if (!LauncherAppsCompat.getInstance(context)
+ .isPackageEnabledForProfile(packageName, myUserHandle)) {
if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+ pendingInfo.launchIntent);
continue;
@@ -240,7 +240,7 @@
// Add the new apps to the model and bind them
if (!addShortcuts.isEmpty()) {
LauncherAppState app = LauncherAppState.getInstance();
- app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
+ app.getModel().addAndBindAddedWorkspaceItems(addShortcuts);
}
}
}
@@ -433,22 +433,16 @@
// Already an activity target
return original;
}
- if (!Utilities.isLauncherAppTarget(original.launchIntent)
- || !original.user.equals(UserHandleCompat.myUserHandle())) {
- // We can only convert shortcuts which point to a main activity in the current user.
+ if (!Utilities.isLauncherAppTarget(original.launchIntent)) {
return original;
}
- PackageManager pm = original.mContext.getPackageManager();
- ResolveInfo info = pm.resolveActivity(original.launchIntent, 0);
-
+ LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(original.mContext)
+ .resolveActivity(original.launchIntent, original.user);
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);
+ return new PendingInstallShortcutInfo(info, original.mContext);
}
}
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index c0c22a3..3e0ae4f 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -18,12 +18,10 @@
import android.content.ComponentName;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ContentWriter;
/**
* Represents an item in the launcher.
@@ -59,7 +57,7 @@
public long container = NO_ID;
/**
- * Iindicates the screen in which the shortcut appears.
+ * Indicates the screen in which the shortcut appears.
*/
public long screenId = -1;
@@ -142,15 +140,15 @@
return getIntent() == null ? null : getIntent().getComponent();
}
- public void writeToValues(ContentValues values) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screenId);
- values.put(LauncherSettings.Favorites.CELLX, cellX);
- values.put(LauncherSettings.Favorites.CELLY, cellY);
- values.put(LauncherSettings.Favorites.SPANX, spanX);
- values.put(LauncherSettings.Favorites.SPANY, spanY);
- values.put(LauncherSettings.Favorites.RANK, rank);
+ public void writeToValues(ContentWriter writer) {
+ writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
+ .put(LauncherSettings.Favorites.CONTAINER, container)
+ .put(LauncherSettings.Favorites.SCREEN, screenId)
+ .put(LauncherSettings.Favorites.CELLX, cellX)
+ .put(LauncherSettings.Favorites.CELLY, cellY)
+ .put(LauncherSettings.Favorites.SPANX, spanX)
+ .put(LauncherSettings.Favorites.SPANY, spanY)
+ .put(LauncherSettings.Favorites.RANK, rank);
}
public void readFromValues(ContentValues values) {
@@ -166,26 +164,15 @@
/**
* Write the fields of this item to the DB
- *
- * @param context A context object to use for getting UserManagerCompat
- * @param values
*/
- void onAddToDatabase(Context context, ContentValues values) {
- writeToValues(values);
- long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
- values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
-
+ void onAddToDatabase(ContentWriter writer) {
if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
// We should never persist an item on the extra empty screen.
throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
}
- }
- static void writeBitmap(ContentValues values, Bitmap bitmap) {
- if (bitmap != null) {
- byte[] data = Utilities.flattenBitmap(bitmap);
- values.put(LauncherSettings.Favorites.ICON, data);
- }
+ writeToValues(writer);
+ writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
}
@Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4672e08..9a5e186 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,9 +18,7 @@
import android.Manifest;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -30,7 +28,6 @@
import android.app.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -48,15 +45,13 @@
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
+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.Handler;
-import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.Trace;
@@ -69,11 +64,12 @@
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -81,8 +77,6 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.OvershootInterpolator;
import android.view.inputmethod.InputMethodManager;
-import android.widget.Advanceable;
-import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -92,6 +86,7 @@
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DefaultAppSearchController;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
@@ -106,10 +101,12 @@
import com.android.launcher3.dynamicui.ExtractedColors;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutsContainer;
@@ -178,9 +175,6 @@
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
"com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
- public static final String ACTION_APPWIDGET_HOST_RESET =
- "com.android.launcher3.intent.ACTION_APPWIDGET_HOST_RESET";
-
// Type: int
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
// Type: int
@@ -211,18 +205,6 @@
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
- private final BroadcastReceiver mUiBroadcastReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_APPWIDGET_HOST_RESET.equals(intent.getAction())) {
- if (mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
- }
- }
- }
- };
-
@Thunk Workspace mWorkspace;
private View mLauncherView;
@Thunk DragLayer mDragLayer;
@@ -250,9 +232,8 @@
// Main container view and the model for the widget tray screen.
@Thunk WidgetsContainerView mWidgetsView;
- @Thunk WidgetsModel mWidgetsModel;
+ @Thunk MultiHashMap<PackageItemInfo, WidgetItem> mAllWidgets;
- private Bundle mSavedState;
// We set the state in both onCreate and then onNewIntent in some cases, which causes both
// scroll issues (because the workspace may not have been measured yet) and extra work.
// Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
@@ -273,27 +254,16 @@
private IconCache mIconCache;
private ExtractedColors mExtractedColors;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
+ private Handler mHandler = new Handler();
private boolean mIsResumeFromActionScreenOff;
- @Thunk boolean mUserPresent = true;
- private boolean mVisible;
- private boolean mHasFocus;
- private boolean mAttached;
+ private boolean mHasFocus = false;
+ private boolean mAttached = false;
/** Maps launcher activity components to their list of shortcut ids. */
private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
private View.OnTouchListener mHapticFeedbackTouchListener;
- // Related to the auto-advancing of widgets
- private final int ADVANCE_MSG = 1;
- private static final int ADVANCE_INTERVAL = 20000;
- private static final int ADVANCE_STAGGER = 250;
-
- private boolean mAutoAdvanceRunning = false;
- private long mAutoAdvanceSentTime;
- private long mAutoAdvanceTimeLeft = -1;
- @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>();
-
// Determines how long to wait after a rotation before restoring the screen orientation to
// match the sensor state.
private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
@@ -304,13 +274,6 @@
// it from the context.
private SharedPreferences mSharedPrefs;
- // Holds the page that we need to animate to, and the icon views that we need to animate up
- // when we scroll to that page on resume.
- @Thunk ImageView mFolderIconImageView;
- private Bitmap mFolderIconBitmap;
- private Canvas mFolderIconCanvas;
- private Rect mRectForFolderAnimation = new Rect();
-
private DeviceProfile mDeviceProfile;
private boolean mMoveToDefaultScreenFromNewIntent;
@@ -352,6 +315,8 @@
private UserEventDispatcher mUserEventDispatcher;
+ private float mLastDispatchTouchEventX = 0.0f;
+
public ViewGroupFocusHelper mFocusHandler;
private boolean mRotationEnabled = false;
@@ -395,10 +360,17 @@
LauncherAppState app = LauncherAppState.getInstance();
// Load configuration-specific DeviceProfile
- mDeviceProfile = getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE ?
- app.getInvariantDeviceProfile().landscapeProfile
- : app.getInvariantDeviceProfile().portraitProfile;
+ mDeviceProfile =
+ getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? app.getInvariantDeviceProfile().landscapeProfile
+ : app.getInvariantDeviceProfile().portraitProfile;
+
+ if (Utilities.ATLEAST_NOUGAT && isInMultiWindowMode()) {
+ Display display = getWindowManager().getDefaultDisplay();
+ Point mwSize = new Point();
+ display.getSize(mwSize);
+ mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+ }
mSharedPrefs = Utilities.getPrefs(this);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
@@ -420,7 +392,7 @@
// LauncherModel load.
mPaused = false;
- setContentView(R.layout.launcher);
+ mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null);
setupViews();
mDeviceProfile.layout(this, false /* notifyListeners */);
@@ -432,8 +404,7 @@
lockAllApps();
- mSavedState = savedInstanceState;
- restoreState(mSavedState);
+ restoreState(savedInstanceState);
if (LauncherAppState.PROFILE_STARTUP) {
Trace.endSection();
@@ -441,11 +412,18 @@
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
- if (!mModel.startLoader(mWorkspace.getRestorePage())) {
+ int currentScreen = PagedView.INVALID_RESTORE_PAGE;
+ if (savedInstanceState != null) {
+ currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
+ }
+ if (!mModel.startLoader(currentScreen)) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.setAlpha(0);
} else {
+ // Pages bound synchronously.
+ mWorkspace.setCurrentPage(currentScreen);
+
setWorkspaceLoading(true);
}
@@ -453,9 +431,6 @@
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
- IntentFilter filter = new IntentFilter(ACTION_APPWIDGET_HOST_RESET);
- registerReceiver(mUiBroadcastReceiver, filter);
-
mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
// In case we are on a device with locked rotation, we should look at preferences to check
// if the user has specifically allowed rotation.
@@ -469,19 +444,32 @@
// we want the screen to auto-rotate based on the current orientation
setOrientation();
+ setContentView(mLauncherView);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
}
@Override
+ public View findViewById(int id) {
+ return mLauncherView.findViewById(id);
+ }
+
+ @Override
public void onExtractedColorsChanged() {
loadExtractedColorsAndColorItems();
}
+ @Override
+ public void onAppWidgetHostReset() {
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
+ }
+ }
+
private void loadExtractedColorsAndColorItems() {
// TODO: do this in pre-N as well, once the extraction part is complete.
- if (Utilities.isNycOrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT) {
mExtractedColors.load(this);
mHotseat.updateColor(mExtractedColors, !mPaused);
mWorkspace.getPageIndicator().updateColor(mExtractedColors);
@@ -582,7 +570,7 @@
}
@Override
- public void onLauncherProviderChange() {
+ public void onLauncherProviderChanged() {
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onLauncherProviderChange();
}
@@ -608,7 +596,7 @@
}
/**
- * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
+ * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to
* ensure the custom content page is added or removed if necessary.
*/
protected void invalidateHasCustomContentToLeft() {
@@ -928,7 +916,7 @@
mLauncherCallbacks.onStop();
}
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
mAppWidgetHost.stopListening();
}
}
@@ -942,7 +930,7 @@
mLauncherCallbacks.onStart();
}
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
mAppWidgetHost.startListening();
}
}
@@ -1227,11 +1215,8 @@
if (keyCode == KeyEvent.KEYCODE_MENU) {
// Ignore the menu key if we are currently dragging or are on the custom content screen
if (!isOnCustomContent() && !mDragController.isDragging()) {
- // Close any open folders
- closeFolder();
-
- // Close any shortcuts containers
- closeShortcutsContainer();
+ // Close any open floating view
+ AbstractFloatingView.closeAllOpenViews(this);
// Stop resizing any widgets
mWorkspace.exitWidgetResizeMode();
@@ -1260,22 +1245,6 @@
}
/**
- * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
- * State
- */
- private static State intToState(int stateOrdinal) {
- State state = State.WORKSPACE;
- final State[] stateValues = State.values();
- for (int i = 0; i < stateValues.length; i++) {
- if (stateValues[i].ordinal() == stateOrdinal) {
- state = stateValues[i];
- break;
- }
- }
- return state;
- }
-
- /**
* Restores the previous state, if it exists.
*
* @param savedState The previous state.
@@ -1285,17 +1254,14 @@
return;
}
- State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+ int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal());
+ State[] stateValues = State.values();
+ State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length)
+ ? stateValues[stateOrdinal] : State.WORKSPACE;
if (state == State.APPS || state == State.WIDGETS) {
mOnResumeState = state;
}
- int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
- PagedView.INVALID_RESTORE_PAGE);
- if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
- mWorkspace.setRestorePage(currentScreen);
- }
-
PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
if (requestArgs != null) {
setWaitingForResult(requestArgs);
@@ -1308,7 +1274,6 @@
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
- mLauncherView = findViewById(R.id.launcher);
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
@@ -1355,7 +1320,7 @@
}
// Setup the drag controller (drop targets have to be added in reverse order in priority)
- mDragController.setDragScoller(mWorkspace);
+ mDragController.setDragScroller(mWorkspace);
mDragController.setScrollView(mDragLayer);
mDragController.setMoveTarget(mWorkspace);
mDragController.addDropTarget(mWorkspace);
@@ -1373,53 +1338,36 @@
private void setupOverviewPanel() {
mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
- // Long-clicking buttons in the overview panel does the same thing as clicking them.
- OnLongClickListener performClickOnLongClick = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- return v.performClick();
- }
- };
-
// Bind wallpaper button actions
View wallpaperButton = findViewById(R.id.wallpaper_button);
- wallpaperButton.setOnClickListener(new OnClickListener() {
+ new OverviewButtonClickListener(LauncherLogProto.WALLPAPER_BUTTON) {
@Override
- public void onClick(View view) {
- if (!mWorkspace.isSwitchingState()) {
- onClickWallpaperPicker(view);
- }
+ public void handleViewClick(View view) {
+ onClickWallpaperPicker(view);
}
- });
- wallpaperButton.setOnLongClickListener(performClickOnLongClick);
+ }.attachTo(wallpaperButton);
wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
// Bind widget button actions
mWidgetsButton = findViewById(R.id.widget_button);
- mWidgetsButton.setOnClickListener(new OnClickListener() {
+ new OverviewButtonClickListener(LauncherLogProto.WIDGETS_BUTTON) {
@Override
- public void onClick(View view) {
- if (!mWorkspace.isSwitchingState()) {
- onClickAddWidgetButton(view);
- }
+ public void handleViewClick(View view) {
+ onClickAddWidgetButton(view);
}
- });
- mWidgetsButton.setOnLongClickListener(performClickOnLongClick);
+ }.attachTo(mWidgetsButton);
mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
// Bind settings actions
View settingsButton = findViewById(R.id.settings_button);
boolean hasSettings = hasSettings();
if (hasSettings) {
- settingsButton.setOnClickListener(new OnClickListener() {
+ new OverviewButtonClickListener(LauncherLogProto.SETTINGS_BUTTON) {
@Override
- public void onClick(View view) {
- if (!mWorkspace.isSwitchingState()) {
- onClickSettingsButton(view);
- }
+ public void handleViewClick(View view) {
+ onClickSettingsButton(view);
}
- });
- settingsButton.setOnLongClickListener(performClickOnLongClick);
+ }.attachTo(settingsButton);
settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
} else {
settingsButton.setVisibility(View.GONE);
@@ -1517,7 +1465,7 @@
}
if (!foundCellSpan) {
- showOutOfSpaceMessage(isHotseatLayout(layout));
+ mWorkspace.onNoCellFound(layout);
return;
}
@@ -1573,10 +1521,6 @@
mWorkspace.addInScreen(hostView, item.container, item.screenId,
item.cellX, item.cellY, item.spanX, item.spanY, insert);
-
- if (!item.isCustomWidget()) {
- addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
- }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1584,9 +1528,7 @@
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mUserPresent = false;
- mDragLayer.clearAllResizeFrames();
- updateAutoAdvanceState();
+ mDragLayer.clearResizeFrame();
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
@@ -1597,9 +1539,6 @@
}
}
mIsResumeFromActionScreenOff = true;
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- mUserPresent = true;
- updateAutoAdvanceState();
}
}
};
@@ -1611,11 +1550,9 @@
// Listen for broadcasts related to user-presence
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
mAttached = true;
- mVisible = true;
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onAttachedToWindow();
@@ -1625,13 +1562,10 @@
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mVisible = false;
-
if (mAttached) {
unregisterReceiver(mReceiver);
mAttached = false;
}
- updateAutoAdvanceState();
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onDetachedFromWindow();
@@ -1639,12 +1573,10 @@
}
public void onWindowVisibilityChanged(int visibility) {
- mVisible = visibility == View.VISIBLE;
- 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) {
+ if (visibility == View.VISIBLE) {
if (!mWorkspaceLoading) {
final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
// We want to let Launcher draw itself at least once before we force it to build
@@ -1679,77 +1611,6 @@
}
}
- @Thunk void sendAdvanceMessage(long delay) {
- mHandler.removeMessages(ADVANCE_MSG);
- Message msg = mHandler.obtainMessage(ADVANCE_MSG);
- mHandler.sendMessageDelayed(msg, delay);
- mAutoAdvanceSentTime = System.currentTimeMillis();
- }
-
- @Thunk void updateAutoAdvanceState() {
- boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
- if (autoAdvanceRunning != mAutoAdvanceRunning) {
- mAutoAdvanceRunning = autoAdvanceRunning;
- if (autoAdvanceRunning) {
- long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft;
- sendAdvanceMessage(delay);
- } else {
- if (!mWidgetsToAdvance.isEmpty()) {
- mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL -
- (System.currentTimeMillis() - mAutoAdvanceSentTime));
- }
- mHandler.removeMessages(ADVANCE_MSG);
- mHandler.removeMessages(0); // Remove messages sent using postDelayed()
- }
- }
- }
-
- @Thunk final Handler mHandler = new Handler(new Handler.Callback() {
-
- @Override
- public boolean handleMessage(Message msg) {
- if (msg.what == ADVANCE_MSG) {
- int i = 0;
- for (View key: mWidgetsToAdvance.keySet()) {
- final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
- final int delay = ADVANCE_STAGGER * i;
- if (v instanceof Advanceable) {
- mHandler.postDelayed(new Runnable() {
- public void run() {
- ((Advanceable) v).advance();
- }
- }, delay);
- }
- i++;
- }
- sendAdvanceMessage(ADVANCE_INTERVAL);
- }
- return true;
- }
- });
-
- private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
- if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
- View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
- if (v instanceof Advanceable) {
- mWidgetsToAdvance.put(hostView, appWidgetInfo);
- ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
- updateAutoAdvanceState();
- }
- }
-
- private void removeWidgetToAutoAdvance(View hostView) {
- if (mWidgetsToAdvance.containsKey(hostView)) {
- mWidgetsToAdvance.remove(hostView);
- updateAutoAdvanceState();
- }
- }
-
- 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 DragLayer getDragLayer() {
return mDragLayer;
}
@@ -1798,13 +1659,6 @@
return mDeviceProfile;
}
- public void closeSystemDialogs() {
- getWindow().closeAllPanels();
-
- // Whatever we were doing is hereby canceled.
- setWaitingForResult(null);
- }
-
@Override
protected void onNewIntent(Intent intent) {
long startTime = 0;
@@ -1819,13 +1673,10 @@
// Check this condition before handling isActionMain, as this will get reset.
boolean shouldMoveToDefaultScreen = alreadyOnHome &&
- mState == State.WORKSPACE && getTopFloatingView() == null;
+ mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null;
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
if (isActionMain) {
- // also will cancel mWaitingForResult.
- closeSystemDialogs();
-
if (mWorkspace == null) {
// Can be cases where mWorkspace is null, this prevents a NPE
return;
@@ -1833,8 +1684,7 @@
// In all these cases, only animate if we're already on home
mWorkspace.exitWidgetResizeMode();
- closeFolder(alreadyOnHome);
- closeShortcutsContainer(alreadyOnHome);
+ AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
exitSpringLoadedDragMode();
// If we are already on home, then just animate back to the workspace,
@@ -1917,11 +1767,9 @@
super.onSaveInstanceState(outState);
outState.putInt(RUNTIME_STATE, mState.ordinal());
- // We close any open folder since it will not be re-opened, and we need to make sure
- // this state is reflected.
- // TODO: Move folderInfo.isOpened out of the model and make it a UI state.
- closeFolder(false);
- closeShortcutsContainer(false);
+ // We close any open folders and shortcut containers since they will not be re-opened,
+ // and we need to make sure this state is reflected.
+ AbstractFloatingView.closeAllOpenViews(this, false);
if (mPendingRequestArgs != null) {
outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
@@ -1939,9 +1787,6 @@
public void onDestroy() {
super.onDestroy();
- // Remove all pending runnables
- mHandler.removeMessages(ADVANCE_MSG);
- mHandler.removeMessages(0);
mWorkspace.removeCallbacks(mBuildLayersRunnable);
mWorkspace.removeFolderListeners();
@@ -1964,15 +1809,11 @@
}
mAppWidgetHost = null;
- mWidgetsToAdvance.clear();
-
TextKeyListener.getInstance().release();
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
.removeAccessibilityStateChangeListener(this);
- unregisterReceiver(mUiBroadcastReceiver);
-
LauncherAnimUtils.onDestroyActivity();
if (mLauncherCallbacks != null) {
@@ -2155,7 +1996,7 @@
protected void moveToCustomContentScreen(boolean animate) {
// Close any folders that may be open.
- closeFolder();
+ AbstractFloatingView.closeAllOpenViews(this, animate);
mWorkspace.moveToCustomContentScreen(animate);
}
@@ -2285,7 +2126,6 @@
} else if (itemInfo instanceof LauncherAppWidgetInfo) {
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
mWorkspace.removeWorkspaceItem(v);
- removeWidgetToAutoAdvance(v);
if (deleteFromDb) {
deleteWidgetInfo(widgetInfo);
}
@@ -2347,21 +2187,19 @@
return;
}
- if (getOpenShortcutsContainer() != null) {
- closeShortcutsContainer();
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+ if (topView != null) {
+ if (topView.getActiveTextView() != null) {
+ topView.getActiveTextView().dispatchBackKey();
+ } else {
+ topView.close(true);
+ }
} else if (isAppsViewVisible()) {
showWorkspace(true);
} else if (isWidgetsViewVisible()) {
showOverviewMode(true);
} else if (mWorkspace.isInOverviewMode()) {
showWorkspace(true);
- } else if (mWorkspace.getOpenFolder() != null) {
- Folder openFolder = mWorkspace.getOpenFolder();
- if (openFolder.isEditingName()) {
- openFolder.dismissEditingName();
- } else {
- closeFolder();
- }
} else {
mWorkspace.exitWidgetResizeMode();
@@ -2611,10 +2449,10 @@
throw new IllegalArgumentException("Input must be a FolderIcon");
}
- FolderIcon folderIcon = (FolderIcon) v;
- if (!folderIcon.getFolderInfo().opened && !folderIcon.getFolder().isDestroyed()) {
+ Folder folder = ((FolderIcon) v).getFolder();
+ if (!folder.isOpen() && !folder.isDestroyed()) {
// Open the requested folder
- openFolder(folderIcon);
+ folder.animateOpen();
}
}
@@ -2636,25 +2474,32 @@
* on the home screen.
*/
public void onClickWallpaperPicker(View v) {
- if (!Utilities.isWallapaperAllowed(this)) {
+ if (!Utilities.isWallpaperAllowed(this)) {
Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
return;
}
- String pickerPackage = getString(R.string.wallpaper_picker_package);
- if (TextUtils.isEmpty(pickerPackage)) {
- pickerPackage = PackageManagerHelper.getWallpaperPickerPackage(getPackageManager());
- }
-
int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
-
setWaitingForResult(new PendingRequestArgs(new ItemInfo()));
Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
- .setPackage(pickerPackage)
.putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
+
+ String pickerPackage = getString(R.string.wallpaper_picker_package);
+ boolean hasTargetPackage = TextUtils.isEmpty(pickerPackage);
+ if (!hasTargetPackage) {
+ intent.setPackage(pickerPackage);
+ }
+
intent.setSourceBounds(getViewBounds(v));
- startActivityForResult(intent, REQUEST_PICK_WALLPAPER, getActivityLaunchOptions(v));
+ try {
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
+ // If there is no target package, use the default intent chooser animation
+ hasTargetPackage ? getActivityLaunchOptions(v) : null);
+ } catch (ActivityNotFoundException e) {
+ setWaitingForResult(null);
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ }
}
/**
@@ -2749,7 +2594,7 @@
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
String id = ((ShortcutInfo) info).getDeepShortcutId();
String packageName = intent.getPackage();
- LauncherAppState.getInstance().getShortcutManager().startShortcut(
+ DeepShortcutManager.getInstance(this).startShortcut(
packageName, id, intent.getSourceBounds(), optsBundle, info.user);
} else {
// Could be launching some bookkeeping activity
@@ -2778,6 +2623,7 @@
}
}
+ @TargetApi(Build.VERSION_CODES.M)
private Bundle getActivityLaunchOptions(View v) {
if (Utilities.ATLEAST_MARSHMALLOW) {
int left = 0, top = 0;
@@ -2854,227 +2700,10 @@
return false;
}
- /**
- * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
- * in the DragLayer in the exact absolute location of the original FolderIcon.
- */
- private void copyFolderIconToImage(FolderIcon fi) {
- final int width = fi.getMeasuredWidth();
- final int height = fi.getMeasuredHeight();
-
- // Lazy load ImageView, Bitmap and Canvas
- if (mFolderIconImageView == null) {
- mFolderIconImageView = new ImageView(this);
- }
- if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
- mFolderIconBitmap.getHeight() != height) {
- mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mFolderIconCanvas = new Canvas(mFolderIconBitmap);
- }
-
- DragLayer.LayoutParams lp;
- if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
- lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
- } else {
- lp = new DragLayer.LayoutParams(width, height);
- }
-
- // The layout from which the folder is being opened may be scaled, adjust the starting
- // view size by this scale factor.
- float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
- lp.customPosition = true;
- lp.x = mRectForFolderAnimation.left;
- lp.y = mRectForFolderAnimation.top;
- lp.width = (int) (scale * width);
- lp.height = (int) (scale * height);
-
- mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
- fi.draw(mFolderIconCanvas);
- mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
- if (fi.getFolder() != null) {
- mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
- mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
- }
- // Just in case this image view is still in the drag layer from a previous animation,
- // we remove it and re-add it.
- if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
- mDragLayer.removeView(mFolderIconImageView);
- }
- mDragLayer.addView(mFolderIconImageView, lp);
- if (fi.getFolder() != null) {
- fi.getFolder().bringToFront();
- }
- }
-
- private void growAndFadeOutFolderIcon(FolderIcon fi) {
- if (fi == null) return;
- FolderInfo info = (FolderInfo) fi.getTag();
- if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- CellLayout cl = (CellLayout) fi.getParent().getParent();
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
- cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
- }
-
- // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
- copyFolderIconToImage(fi);
- fi.setVisibility(View.INVISIBLE);
-
- ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
- mFolderIconImageView, 0, 1.5f, 1.5f);
- if (Utilities.ATLEAST_LOLLIPOP) {
- oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
- }
- oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
- oa.start();
- }
-
- private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) {
- if (fi == null) return;
- final CellLayout cl = (CellLayout) fi.getParent().getParent();
-
- // We remove and re-draw the FolderIcon in-case it has changed
- mDragLayer.removeView(mFolderIconImageView);
- copyFolderIconToImage(fi);
-
- if (cl != null) {
- cl.clearFolderLeaveBehind();
- }
-
- ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1);
- oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
- oa.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (cl != null) {
- // Remove the ImageView copy of the FolderIcon and make the original visible.
- mDragLayer.removeView(mFolderIconImageView);
- fi.setVisibility(View.VISIBLE);
- }
- }
- });
- oa.start();
- if (!animate) {
- oa.end();
- }
- }
-
- /**
- * Opens the user folder described by the specified tag. The opening of the folder
- * is animated relative to the specified View. If the View is null, no animation
- * is played.
- *
- * @param folderIcon The FolderIcon describing the folder to open.
- */
- 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) {
- mDragLayer.addView(folder);
- mDragController.addDropTarget(folder);
- } else {
- Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
- folder.getParent() + ").");
- }
- folder.animateOpen();
-
- growAndFadeOutFolderIcon(folderIcon);
-
- // Notify the accessibility manager that this folder "window" has appeared and occluded
- // the workspace items
- folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- }
-
- public void closeFolder() {
- closeFolder(true);
- }
-
- public void closeFolder(boolean animate) {
- Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
- if (folder != null) {
- if (folder.isEditingName()) {
- folder.dismissEditingName();
- }
- closeFolder(folder, animate);
- }
- }
-
- public void closeFolder(Folder folder, boolean animate) {
- animate &= !Utilities.isPowerSaverOn(this);
-
- folder.getInfo().opened = false;
-
- ViewGroup parent = (ViewGroup) folder.getParent().getParent();
- if (parent != null) {
- FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
- shrinkAndFadeInFolderIcon(fi, animate);
- if (fi != null) {
- ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
- }
- }
- if (animate) {
- folder.animateClosed();
- } else {
- folder.close(false);
- }
-
- // Notify the accessibility manager that this folder "window" has disappeared and no
- // longer occludes the workspace items
- getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
-
- public void closeShortcutsContainer() {
- closeShortcutsContainer(true);
- }
-
- public void closeShortcutsContainer(boolean animate) {
- DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer();
- if (deepShortcutsContainer != null) {
- if (animate) {
- deepShortcutsContainer.animateClose();
- } else {
- deepShortcutsContainer.close();
- }
- }
- }
-
- public View getTopFloatingView() {
- View topView = getOpenShortcutsContainer();
- if (topView == null) {
- topView = getWorkspace().getOpenFolder();
- }
- return topView;
- }
-
- /**
- * @return The open shortcuts container, or null if there is none
- */
- public DeepShortcutsContainer getOpenShortcutsContainer() {
- // Iterate in reverse order. Shortcuts container is added later to the dragLayer,
- // and will be one of the last views.
- for (int i = mDragLayer.getChildCount() - 1; i >= 0; i--) {
- View child = mDragLayer.getChildAt(i);
- if (child instanceof DeepShortcutsContainer
- && ((DeepShortcutsContainer) child).isOpen()) {
- return (DeepShortcutsContainer) child;
- }
- }
- return null;
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ mLastDispatchTouchEventX = ev.getX();
+ return super.dispatchTouchEvent(ev);
}
@Override
@@ -3089,9 +2718,16 @@
return true;
}
+
+ boolean ignoreLongPressToOverview =
+ mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
+
if (v instanceof Workspace) {
if (!mWorkspace.isInOverviewMode()) {
- if (!mWorkspace.isTouchActive()) {
+ if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.LONGPRESS,
+ LauncherLogProto.Action.NONE, LauncherLogProto.WORKSPACE,
+ mWorkspace.getCurrentPage());
showOverviewMode(true);
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -3118,13 +2754,21 @@
if (!mDragController.isDragging()) {
if (itemUnderLongClick == null) {
// User long pressed on empty space
- mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
if (mWorkspace.isInOverviewMode()) {
mWorkspace.startReordering(v);
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.LONGPRESS,
+ LauncherLogProto.Action.NONE, LauncherLogProto.OVERVIEW);
} else {
+ if (ignoreLongPressToOverview) {
+ return false;
+ }
+ getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.LONGPRESS,
+ LauncherLogProto.Action.NONE, LauncherLogProto.WORKSPACE,
+ mWorkspace.getCurrentPage());
showOverviewMode(true);
}
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
} else {
final boolean isAllAppsButton =
!FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
@@ -3132,17 +2776,7 @@
longClickCellInfo.cellX, longClickCellInfo.cellY));
if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
// User long pressed on an item
- DragOptions dragOptions = new DragOptions();
- if (itemUnderLongClick instanceof BubbleTextView) {
- BubbleTextView icon = (BubbleTextView) itemUnderLongClick;
- if (icon.hasDeepShortcuts()) {
- DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
- if (dsc != null) {
- dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
- }
- }
- }
- mWorkspace.startDrag(longClickCellInfo, dragOptions);
+ mWorkspace.startDrag(longClickCellInfo, new DragOptions());
}
}
}
@@ -3221,10 +2855,6 @@
// Change the state *after* we've called all the transition code
mState = State.WORKSPACE;
- // Resume the auto-advance of widgets
- mUserPresent = true;
- updateAutoAdvanceState();
-
if (changed) {
// Send an accessibility event to announce the context change
getWindow().getDecorView()
@@ -3321,20 +2951,14 @@
}
if (toState == State.APPS) {
- mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
- focusSearchBar);
+ mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
} else {
- mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
+ mStateTransitionAnimation.startAnimationToWidgets(animated);
}
// Change the state *after* we've called all the transition code
mState = toState;
-
- // Pause the auto-advance of widgets until we are out of AllApps
- mUserPresent = false;
- updateAutoAdvanceState();
- closeFolder();
- closeShortcutsContainer();
+ AbstractFloatingView.closeAllOpenViews(this);
// Send an accessibility event to announce the context change
getWindow().getDecorView()
@@ -3347,7 +2971,7 @@
* new state.
*/
public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
- boolean animated, HashMap<View, Integer> layerViews) {
+ boolean animated, AnimationLayerSet layerViews) {
Workspace.State fromState = mWorkspace.getState();
Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
updateInteraction(fromState, toState);
@@ -3558,7 +3182,6 @@
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
- mWidgetsToAdvance.clear();
if (mHotseat != null) {
mHotseat.resetLayout();
}
@@ -3806,7 +3429,7 @@
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+ " belongs to component " + item.providerName
- + ", as the povider is null");
+ + ", as the provider is null");
}
LauncherModel.deleteItemFromDatabase(this, item);
return;
@@ -3981,14 +3604,6 @@
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Page bind completed");
}
- if (mSavedState != null) {
- if (!mWorkspace.hasFocus()) {
- mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
- }
-
- mSavedState = null;
- }
-
mWorkspace.restoreInstanceStateForRemainingPages();
setWorkspaceLoading(false);
@@ -4239,22 +3854,22 @@
}
}
- private Runnable mBindWidgetModelRunnable = new Runnable() {
+ private Runnable mBindAllWidgetsRunnable = new Runnable() {
public void run() {
- bindWidgetsModel(mWidgetsModel);
+ bindAllWidgets(mAllWidgets);
}
};
@Override
- public void bindWidgetsModel(WidgetsModel model) {
- if (waitUntilResume(mBindWidgetModelRunnable, true)) {
- mWidgetsModel = model;
+ public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) {
+ if (waitUntilResume(mBindAllWidgetsRunnable, true)) {
+ mAllWidgets = allWidgets;
return;
}
- if (mWidgetsView != null && model != null) {
- mWidgetsView.addWidgets(model);
- mWidgetsModel = null;
+ if (mWidgetsView != null && allWidgets != null) {
+ mWidgetsView.setWidgets(allWidgets);
+ mAllWidgets = null;
}
}
@@ -4410,7 +4025,6 @@
*/
public void dumpState() {
Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
- Log.d(TAG, "mSavedState=" + mSavedState);
Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs);
Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult);
@@ -4457,6 +4071,65 @@
}
}
+ @Override
+ @TargetApi(Build.VERSION_CODES.N)
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+
+ ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
+ if (mState == State.WORKSPACE) {
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
+ KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+ }
+ View currentFocus = getCurrentFocus();
+ if (new CustomActionsPopup(this, currentFocus).canShow()) {
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
+ KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
+ }
+ if (currentFocus instanceof BubbleTextView &&
+ ((BubbleTextView) currentFocus).hasDeepShortcuts()) {
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut),
+ KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
+ }
+ if (!shortcutInfos.isEmpty()) {
+ data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
+ }
+
+ super.onProvideKeyboardShortcuts(data, menu, deviceId);
+ }
+
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_A:
+ if (mState == State.WORKSPACE) {
+ showAppsView(true, true, false);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_S: {
+ View focusedView = getCurrentFocus();
+ if (focusedView instanceof BubbleTextView
+ && focusedView.getTag() instanceof ItemInfo
+ && mAccessibilityDelegate.performAction(focusedView,
+ (ItemInfo) focusedView.getTag(),
+ LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
+ DeepShortcutsContainer.getOpen(this).requestFocus();
+ return true;
+ }
+ break;
+ }
+ case KeyEvent.KEYCODE_O:
+ if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
+ return true;
+ }
+ break;
+ }
+ }
+ return super.onKeyShortcut(keyCode, event);
+ }
+
public static CustomAppWidget getCustomAppWidget(String name) {
return sCustomAppWidgets.get(name);
}
@@ -4465,14 +4138,6 @@
return sCustomAppWidgets;
}
- public static List<View> getFolderContents(View icon) {
- if (icon instanceof FolderIcon) {
- return ((FolderIcon) icon).getFolder().getItemsInReadingOrder();
- } else {
- return Collections.EMPTY_LIST;
- }
- }
-
public static Launcher getLauncher(Context context) {
if (context instanceof Launcher) {
return (Launcher) context;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 7861a10..5937d78 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,8 +28,6 @@
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutCache;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
@@ -44,7 +42,6 @@
@Thunk final LauncherModel mModel;
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
- private final DeepShortcutManager mDeepShortcutManager;
@Thunk boolean mWallpaperChangedSinceLastCheck;
@@ -86,10 +83,10 @@
private LauncherAppState() {
if (sContext == null) {
- throw new IllegalStateException("LauncherAppState inited before app context set");
+ throw new IllegalStateException("LauncherAppState initiated before app context set");
}
- Log.v(Launcher.TAG, "LauncherAppState inited");
+ Log.v(Launcher.TAG, "LauncherAppState initiated");
if (TestingUtils.MEMORY_DUMP_ENABLED) {
TestingUtils.startTrackingMemory(sContext);
@@ -98,10 +95,9 @@
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
- mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
- mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
+ mModel = new LauncherModel(this, mIconCache, mAppFilter);
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
@@ -115,7 +111,7 @@
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
// For extracting colors from the wallpaper
- if (Utilities.isNycOrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT) {
// TODO: add a broadcast entry to the manifest for pre-N.
filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
}
@@ -173,10 +169,6 @@
return mWidgetCache;
}
- public DeepShortcutManager getShortcutManager() {
- return mDeepShortcutManager;
- }
-
public boolean hasWallpaperChangedSinceLastCheck() {
boolean result = mWallpaperChangedSinceLastCheck;
mWallpaperChangedSinceLastCheck = false;
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index d3e5350..657b024 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -20,10 +20,8 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
+import android.util.SparseArray;
import android.view.LayoutInflater;
-import android.view.View;
import java.util.ArrayList;
@@ -36,6 +34,7 @@
public class LauncherAppWidgetHost extends AppWidgetHost {
private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>();
+ private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
private Launcher mLauncher;
@@ -45,9 +44,11 @@
}
@Override
- protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+ protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- return new LauncherAppWidgetHostView(context);
+ LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+ mViews.put(appWidgetId, view);
+ return view;
}
@Override
@@ -55,15 +56,13 @@
try {
super.startListening();
} catch (Exception e) {
- if (e.getCause() instanceof TransactionTooLargeException ||
- e.getCause() instanceof DeadObjectException) {
- // We're willing to let this slide. The exception is being caused by the list of
- // RemoteViews which is being passed back. The startListening relationship will
- // have been established by this point, and we will end up populating the
- // widgets upon bind anyway. See issue 14255011 for more context.
- } else {
+ if (!Utilities.isBinderSizeError(e)) {
throw new RuntimeException(e);
}
+ // We're willing to let this slide. The exception is being caused by the list of
+ // RemoteViews which is being passed back. The startListening relationship will
+ // have been established by this point, and we will end up populating the
+ // widgets upon bind anyway. See issue 14255011 for more context.
}
}
@@ -98,7 +97,24 @@
lahv.updateLastInflationOrientation();
return lahv;
} else {
- return super.createView(context, appWidgetId, appWidget);
+ try {
+ return super.createView(context, appWidgetId, appWidget);
+ } catch (Exception e) {
+ if (!Utilities.isBinderSizeError(e)) {
+ throw new RuntimeException(e);
+ }
+
+ // If the exception was thrown while fetching the remote views, let the view stay.
+ // This will ensure that if the widget posts a valid update later, the view
+ // will update.
+ LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+ if (view == null) {
+ view = onCreateView(mLauncher, appWidgetId, appWidget);
+ }
+ view.setAppWidget(appWidgetId, appWidget);
+ view.switchToErrorView();
+ return view;
+ }
}
}
@@ -114,4 +130,16 @@
// launcher spans accordingly.
info.initSpans();
}
+
+ @Override
+ public void deleteAppWidgetId(int appWidgetId) {
+ super.deleteAppWidgetId(appWidgetId);
+ mViews.remove(appWidgetId);
+ }
+
+ @Override
+ protected void clearViews() {
+ super.clearViews();
+ mViews.clear();
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index ed1079f..a4ea449 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -20,6 +20,10 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -28,17 +32,29 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Advanceable;
import android.widget.RemoteViews;
import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
+import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
/**
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener {
+ private static final String TAG = "LauncherWidgetHostView";
+
+ // Related to the auto-advancing of widgets
+ private static final long ADVANCE_INTERVAL = 20000;
+ private static final long ADVANCE_STAGGER = 250;
+
+ // Maintains a list of widget ids which are supposed to be auto advanced.
+ private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray();
+
LayoutInflater mInflater;
private CheckLongPressHelper mLongPressHelper;
@@ -52,21 +68,33 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
- protected int mErrorViewId = R.layout.appwidget_error;
+ private boolean mIsAttachedToWindow;
+ private boolean mIsAutoAdvanceRegistered;
+ private Runnable mAutoAdvanceRunnable;
public LauncherAppWidgetHostView(Context context) {
super(context);
mContext = context;
mLongPressHelper = new CheckLongPressHelper(this);
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = LayoutInflater.from(context);
setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate());
setBackgroundResource(R.drawable.widget_internal_focus_bg);
+
+ if (Utilities.isAtLeastO()) {
+ try {
+ Method asyncMethod = AppWidgetHostView.class
+ .getMethod("setAsyncExecutor", Executor.class);
+ asyncMethod.invoke(this, Utilities.THREAD_POOL_EXECUTOR);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to set async executor", e);
+ }
+ }
}
@Override
protected View getErrorView() {
- return mInflater.inflate(mErrorViewId, this, false);
+ return mInflater.inflate(R.layout.appwidget_error, this, false);
}
public void updateLastInflationOrientation() {
@@ -78,6 +106,9 @@
// Store the orientation in which the widget was inflated
updateLastInflationOrientation();
super.updateAppWidget(remoteViews);
+
+ // The provider info or the views might have changed.
+ checkIfAutoAdvance();
}
public boolean isReinflateRequired() {
@@ -153,6 +184,19 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+
+ mIsAttachedToWindow = true;
+ checkIfAutoAdvance();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // We can't directly use isAttachedToWindow() here, as this is called before the internal
+ // state is updated. So isAttachedToWindow() will return true until next frame.
+ mIsAttachedToWindow = false;
+ checkIfAutoAdvance();
}
@Override
@@ -171,10 +215,6 @@
return info;
}
- public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() {
- return (LauncherAppWidgetProviderInfo) getAppWidgetInfo();
- }
-
@Override
public void onTouchComplete() {
if (!mLongPressHelper.hasPerformedLongPress()) {
@@ -276,6 +316,11 @@
setSelected(childIsFocused);
}
+ public void switchToErrorView() {
+ // Update the widget with 0 Layout id, to reset the view to error view.
+ updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
try {
@@ -284,8 +329,7 @@
post(new Runnable() {
@Override
public void run() {
- // Update the widget with 0 Layout id, to reset the view to error view.
- updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ switchToErrorView();
}
});
}
@@ -296,4 +340,79 @@
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(getClass().getName());
}
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ maybeRegisterAutoAdvance();
+ }
+
+ private void checkIfAutoAdvance() {
+ boolean isAutoAdvance = false;
+ Advanceable target = getAdvanceable();
+ if (target != null) {
+ isAutoAdvance = true;
+ target.fyiWillBeAdvancedByHostKThx();
+ }
+
+ boolean wasAutoAdvance = sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0;
+ if (isAutoAdvance != wasAutoAdvance) {
+ if (isAutoAdvance) {
+ sAutoAdvanceWidgetIds.put(getAppWidgetId(), true);
+ } else {
+ sAutoAdvanceWidgetIds.delete(getAppWidgetId());
+ }
+ maybeRegisterAutoAdvance();
+ }
+ }
+
+ private Advanceable getAdvanceable() {
+ AppWidgetProviderInfo info = getAppWidgetInfo();
+ if (info == null || info.autoAdvanceViewId == NO_ID || !mIsAttachedToWindow) {
+ return null;
+ }
+ View v = findViewById(info.autoAdvanceViewId);
+ return (v instanceof Advanceable) ? (Advanceable) v : null;
+ }
+
+ private void maybeRegisterAutoAdvance() {
+ Handler handler = getHandler();
+ boolean shouldRegisterAutoAdvance = getWindowVisibility() == VISIBLE && handler != null
+ && (sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0);
+ if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
+ mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
+ if (mAutoAdvanceRunnable == null) {
+ mAutoAdvanceRunnable = new Runnable() {
+ @Override
+ public void run() {
+ runAutoAdvance();
+ }
+ };
+ }
+
+ handler.removeCallbacks(mAutoAdvanceRunnable);
+ scheduleNextAdvance();
+ }
+ }
+
+ private void scheduleNextAdvance() {
+ if (!mIsAutoAdvanceRegistered) {
+ return;
+ }
+ long now = SystemClock.uptimeMillis();
+ long advanceTime = now + (ADVANCE_INTERVAL - (now % ADVANCE_INTERVAL)) +
+ ADVANCE_STAGGER * sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId());
+ Handler handler = getHandler();
+ if (handler != null) {
+ handler.postAtTime(mAutoAdvanceRunnable, advanceTime);
+ }
+ }
+
+ private void runAutoAdvance() {
+ Advanceable target = getAdvanceable();
+ if (target != null) {
+ target.advance();
+ }
+ scheduleNextAdvance();
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 66d8957..2218767 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -18,11 +18,10 @@
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
/**
* Represents a widget (either instantiated or about to be) in the Launcher.
@@ -84,12 +83,12 @@
/**
* Indicates the restore status of the widget.
*/
- int restoreStatus;
+ public int restoreStatus;
/**
* Indicates the installation progress of the widget provider
*/
- int installProgress = -1;
+ public int installProgress = -1;
/**
* Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}.
@@ -98,7 +97,7 @@
private boolean mHasNotifiedInitialWidgetSizeChanged;
- LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
+ public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
if (appWidgetId == CUSTOM_WIDGET_ID) {
itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
} else {
@@ -117,18 +116,22 @@
restoreStatus = RESTORE_COMPLETED;
}
+ /** Used for testing **/
+ public LauncherAppWidgetInfo() {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ }
+
public boolean isCustomWidget() {
return appWidgetId == CUSTOM_WIDGET_ID;
}
@Override
- void onAddToDatabase(Context context, ContentValues values) {
- super.onAddToDatabase(context, values);
- values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
- values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
- values.put(LauncherSettings.Favorites.RESTORED, restoreStatus);
- values.put(LauncherSettings.Favorites.INTENT,
- bindOptions == null ? null : bindOptions.toUri(0));
+ void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
+ .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
+ .put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+ .put(LauncherSettings.Favorites.INTENT, bindOptions);
}
/**
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
deleted file mode 100644
index c1282b5..0000000
--- a/src/com/android/launcher3/LauncherClings.java
+++ /dev/null
@@ -1,30 +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.Context;
-
-@Deprecated
-public class LauncherClings {
- private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
-
- public static void markFirstRunClingDismissed(Context ctx) {
- Utilities.getPrefs(ctx).edit()
- .putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true)
- .apply();
- }
-}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3ac9773..cfb28f9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -27,7 +27,6 @@
import android.content.Intent.ShortcutIconResource;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -43,7 +42,6 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.util.MutableInt;
-import android.util.Pair;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -57,8 +55,20 @@
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.ExtendedModelTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.SdCardAvailableReceiver;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.PackageInstallStateChangedTask;
+import com.android.launcher3.model.PackageUpdatedTask;
+import com.android.launcher3.model.ShortcutsChangedTask;
+import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.provider.ImportDataTask;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -66,15 +76,15 @@
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.CursorIconInfo;
-import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.StringFilter;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -82,7 +92,6 @@
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -90,7 +99,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -142,16 +150,14 @@
// Entire list of widgets.
private final WidgetsModel mBgWidgetsModel;
- // Maps all launcher activities to the id's of their shortcuts (if they have any).
- private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
-
private boolean mHasShortcutHostPermission;
// Runnable to check if the shortcuts permission has changed.
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
@Override
public void run() {
if (mDeepShortcutsLoaded) {
- boolean hasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+ boolean hasShortcutHostPermission =
+ DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
if (hasShortcutHostPermission != mHasShortcutHostPermission) {
mApp.reloadWorkspace();
}
@@ -159,43 +165,15 @@
}
};
- // The lock that must be acquired before referencing any static bg data structures. Unlike
- // other locks, this one can generally be held long-term because we never expect any of these
- // static data structures to be referenced outside of the worker thread except on the first
- // load after configuration change.
- static final Object sBgLock = new Object();
-
- // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
- // LauncherModel to their ids
- static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
-
- // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
- // created by LauncherModel that are directly on the home screen (however, no widgets or
- // shortcuts within folders).
- static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
-
- // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
- static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
- new ArrayList<LauncherAppWidgetInfo>();
-
- // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
- static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
-
- // sBgWorkspaceScreens is the ordered set of workspace screens.
- static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
-
- // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
- // times it is pinned.
- static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
-
- // sPendingPackages is a set of packages which could be on sdcard and are not available yet
- static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
- new HashMap<UserHandleCompat, HashSet<String>>();
+ /**
+ * All the static data should be accessed on the background thread, A lock should be acquired
+ * on this object when accessing any data from this model.
+ */
+ static final BgDataModel sBgDataModel = new BgDataModel();
// </ only access in worker thread >
- private IconCache mIconCache;
- private DeepShortcutManager mDeepShortcutManager;
+ private final IconCache mIconCache;
private final LauncherAppsCompat mLauncherApps;
private final UserManagerCompat mUserManager;
@@ -226,24 +204,18 @@
UserHandleCompat user);
public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
public void notifyWidgetProvidersChanged();
- public void bindWidgetsModel(WidgetsModel model);
+ public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
public void onPageBoundSynchronously(int page);
public void executeOnNextDraw(ViewOnDrawExecutor executor);
public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
}
- public interface ItemInfoFilter {
- public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
- }
-
- LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
- DeepShortcutManager deepShortcutManager) {
+ LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
Context context = app.getContext();
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
+ mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
mIconCache = iconCache;
- mDeepShortcutManager = deepShortcutManager;
mLauncherApps = LauncherAppsCompat.getInstance(context);
mUserManager = UserManagerCompat.getInstance(context);
@@ -271,294 +243,26 @@
}
}
- public void setPackageState(final PackageInstallInfo installInfo) {
- Runnable updateRunnable = new Runnable() {
-
- @Override
- public void run() {
- synchronized (sBgLock) {
- final HashSet<ItemInfo> updates = new HashSet<>();
-
- if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
- // Ignore install success events as they are handled by Package add events.
- return;
- }
-
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.getTargetComponent();
- if (si.isPromise() && (cn != null)
- && installInfo.packageName.equals(cn.getPackageName())) {
- si.setInstallProgress(installInfo.progress);
-
- if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
- // Mark this info as broken.
- si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
- }
- updates.add(si);
- }
- }
- }
-
- for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
- if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
- widget.installProgress = installInfo.progress;
- updates.add(widget);
- }
- }
-
- if (!updates.isEmpty()) {
- // Push changes to the callback.
- Runnable r = new Runnable() {
- public void run() {
- Callbacks callbacks = getCallback();
- if (callbacks != null) {
- callbacks.bindRestoreItemsChange(updates);
- }
- }
- };
- mHandler.post(r);
- }
- }
- }
- };
- runOnWorkerThread(updateRunnable);
+ public void setPackageState(PackageInstallInfo installInfo) {
+ enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
}
/**
* Updates the icons and label of all pending icons for the provided package name.
*/
public void updateSessionDisplayInfo(final String packageName) {
- Runnable updateRunnable = new Runnable() {
-
- @Override
- public void run() {
- synchronized (sBgLock) {
- ArrayList<ShortcutInfo> updates = new ArrayList<>();
- UserHandleCompat user = UserHandleCompat.myUserHandle();
-
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo) {
- ShortcutInfo si = (ShortcutInfo) info;
- ComponentName cn = si.getTargetComponent();
- if (si.isPromise() && (cn != null)
- && packageName.equals(cn.getPackageName())) {
- if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
- // For auto install apps update the icon as well as label.
- mIconCache.getTitleAndIcon(si,
- si.promisedIntent, user,
- si.shouldUseLowResIcon());
- } else {
- // Only update the icon for restored apps.
- si.updateIcon(mIconCache);
- }
- updates.add(si);
- }
- }
- }
-
- bindUpdatedShortcuts(updates, user);
- }
- }
- };
- runOnWorkerThread(updateRunnable);
- }
-
- public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
- final Callbacks callbacks = getCallback();
-
- if (allAppsApps == null) {
- throw new RuntimeException("allAppsApps must not be null");
- }
- if (allAppsApps.isEmpty()) {
- return;
- }
-
- // Process the newly added applications and add them to the database first
- Runnable r = new Runnable() {
- public void run() {
- runOnMainThread(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAppsAdded(null, null, null, allAppsApps);
- }
- }
- });
- }
- };
- runOnWorkerThread(r);
- }
-
- private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
- int[] xy, int spanX, int spanY) {
- LauncherAppState app = LauncherAppState.getInstance();
- InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
- GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
- if (occupiedPos != null) {
- for (ItemInfo r : occupiedPos) {
- occupied.markCells(r, true);
- }
- }
- return occupied.findVacantCell(xy, spanX, spanY);
- }
-
- /**
- * Find a position on the screen for the given size or adds a new screen.
- * @return screenId and the coordinates for the item.
- */
- @Thunk Pair<Long, int[]> findSpaceForItem(
- Context context,
- ArrayList<Long> workspaceScreens,
- ArrayList<Long> addedWorkspaceScreensFinal,
- int spanX, int spanY) {
- LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
-
- // Use sBgItemsIdMap as all the items are already loaded.
- assertWorkspaceLoaded();
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- ArrayList<ItemInfo> items = screenItems.get(info.screenId);
- if (items == null) {
- items = new ArrayList<>();
- screenItems.put(info.screenId, items);
- }
- items.add(info);
- }
- }
- }
-
- // Find appropriate space for the item.
- long screenId = 0;
- int[] cordinates = new int[2];
- boolean found = false;
-
- int screenCount = workspaceScreens.size();
- // First check the preferred screen.
- int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
- if (preferredScreenIndex < screenCount) {
- screenId = workspaceScreens.get(preferredScreenIndex);
- found = findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY);
- }
-
- if (!found) {
- // Search on any of the screens starting from the first screen.
- for (int screen = 1; screen < screenCount; screen++) {
- screenId = workspaceScreens.get(screen);
- if (findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY)) {
- // We found a space for it
- found = true;
- break;
- }
- }
- }
-
- if (!found) {
- // Still no position found. Add a new screen to the end.
- screenId = LauncherSettings.Settings.call(context.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getLong(LauncherSettings.Settings.EXTRA_VALUE);
-
- // Save the screen id for binding in the workspace
- workspaceScreens.add(screenId);
- addedWorkspaceScreensFinal.add(screenId);
-
- // If we still can't find an empty space, then God help us all!!!
- if (!findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY)) {
- throw new RuntimeException("Can't find space to add the item");
- }
- }
- return Pair.create(screenId, cordinates);
+ HashSet<String> packages = new HashSet<>();
+ packages.add(packageName);
+ enqueueModelUpdateTask(new CacheDataUpdatedTask(
+ CacheDataUpdatedTask.OP_SESSION_UPDATE, UserHandleCompat.myUserHandle(), packages));
}
/**
* Adds the provided items to the workspace.
*/
- public void addAndBindAddedWorkspaceItems(final Context context,
+ public void addAndBindAddedWorkspaceItems(
final ArrayList<? extends ItemInfo> workspaceApps) {
- final Callbacks callbacks = getCallback();
- if (workspaceApps.isEmpty()) {
- return;
- }
- // Process the newly added applications and add them to the database first
- Runnable r = new Runnable() {
- public void run() {
- final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
- final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
-
- // 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 = loadWorkspaceScreensDb(context);
- synchronized(sBgLock) {
- for (ItemInfo item : workspaceApps) {
- if (item instanceof ShortcutInfo) {
- // Short-circuit this logic if the icon exists somewhere on the workspace
- if (shortcutExists(context, item.getIntent(), item.user)) {
- continue;
- }
- }
-
- // Find appropriate space for the item.
- Pair<Long, int[]> coords = findSpaceForItem(context,
- workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
- long screenId = coords.first;
- int[] cordinates = coords.second;
-
- ItemInfo itemInfo;
- if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
- itemInfo = item;
- } else if (item instanceof AppInfo) {
- itemInfo = ((AppInfo) item).makeShortcut();
- } else {
- throw new RuntimeException("Unexpected info type");
- }
-
- // Add the shortcut to the db
- addItemToDatabase(context, itemInfo,
- LauncherSettings.Favorites.CONTAINER_DESKTOP,
- screenId, cordinates[0], cordinates[1]);
- // Save the ShortcutInfo for binding in the workspace
- addedShortcutsFinal.add(itemInfo);
- }
- }
-
- // Update the workspace screens
- updateWorkspaceScreenOrder(context, workspaceScreens);
-
- if (!addedShortcutsFinal.isEmpty()) {
- runOnMainThread(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
- final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
- if (!addedShortcutsFinal.isEmpty()) {
- ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
- long lastScreenId = info.screenId;
- for (ItemInfo i : addedShortcutsFinal) {
- if (i.screenId == lastScreenId) {
- addAnimated.add(i);
- } else {
- addNotAnimated.add(i);
- }
- }
- }
- callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
- addNotAnimated, addAnimated, null);
- }
- }
- });
- }
- }
- };
- runOnWorkerThread(r);
+ enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps));
}
/**
@@ -578,7 +282,7 @@
static void checkItemInfoLocked(
final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
- ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+ ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null && item != modelItem) {
// check all the data is consistent
if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
@@ -619,7 +323,7 @@
final long itemId = item.id;
Runnable r = new Runnable() {
public void run() {
- synchronized (sBgLock) {
+ synchronized (sBgDataModel) {
checkItemInfoLocked(itemId, item, stackTrace);
}
}
@@ -627,7 +331,7 @@
runOnWorkerThread(r);
}
- static void updateItemInDatabaseHelper(Context context, final ContentValues values,
+ static void updateItemInDatabaseHelper(Context context, final ContentWriter writer,
final ItemInfo item, final String callingFunction) {
final long itemId = item.id;
final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
@@ -636,7 +340,7 @@
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
Runnable r = new Runnable() {
public void run() {
- cr.update(uri, values, null, null);
+ cr.update(uri, writer.getValues(), null, null);
updateItemArrays(item, itemId, stackTrace);
}
};
@@ -675,13 +379,13 @@
static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
// Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
+ synchronized (sBgDataModel) {
checkItemInfoLocked(itemId, item, stackTrace);
if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
// Item is in a folder, make sure this folder exists
- if (!sBgFolders.containsKey(item.container)) {
+ if (!sBgDataModel.folders.containsKey(item.container)) {
// An items container is being set to a that of an item which is not in
// the list of Folders.
String msg = "item: " + item + " container being set to: " +
@@ -693,7 +397,7 @@
// Items are added/removed from the corresponding FolderInfo elsewhere, such
// as in Workspace.onDrop. Here, we just add/remove them from the list of items
// that are on the desktop, as appropriate
- ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+ ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
if (modelItem != null &&
(modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
@@ -702,15 +406,15 @@
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- if (!sBgWorkspaceItems.contains(modelItem)) {
- sBgWorkspaceItems.add(modelItem);
+ if (!sBgDataModel.workspaceItems.contains(modelItem)) {
+ sBgDataModel.workspaceItems.add(modelItem);
}
break;
default:
break;
}
} else {
- sBgWorkspaceItems.remove(modelItem);
+ sBgDataModel.workspaceItems.remove(modelItem);
}
}
}
@@ -734,14 +438,14 @@
item.screenId = screenId;
}
- final ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.CONTAINER, item.container);
- values.put(LauncherSettings.Favorites.CELLX, item.cellX);
- values.put(LauncherSettings.Favorites.CELLY, item.cellY);
- values.put(LauncherSettings.Favorites.RANK, item.rank);
- values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+ final ContentWriter writer = new ContentWriter(context)
+ .put(LauncherSettings.Favorites.CONTAINER, item.container)
+ .put(LauncherSettings.Favorites.CELLX, item.cellX)
+ .put(LauncherSettings.Favorites.CELLY, item.cellY)
+ .put(LauncherSettings.Favorites.RANK, item.rank)
+ .put(LauncherSettings.Favorites.SCREEN, item.screenId);
- updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
+ updateItemInDatabaseHelper(context, writer, item, "moveItemInDatabase");
}
/**
@@ -801,79 +505,25 @@
item.screenId = screenId;
}
- final ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites.CONTAINER, item.container);
- values.put(LauncherSettings.Favorites.CELLX, item.cellX);
- values.put(LauncherSettings.Favorites.CELLY, item.cellY);
- values.put(LauncherSettings.Favorites.RANK, item.rank);
- values.put(LauncherSettings.Favorites.SPANX, item.spanX);
- values.put(LauncherSettings.Favorites.SPANY, item.spanY);
- values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+ final ContentWriter writer = new ContentWriter(context)
+ .put(LauncherSettings.Favorites.CONTAINER, item.container)
+ .put(LauncherSettings.Favorites.CELLX, item.cellX)
+ .put(LauncherSettings.Favorites.CELLY, item.cellY)
+ .put(LauncherSettings.Favorites.RANK, item.rank)
+ .put(LauncherSettings.Favorites.SPANX, item.spanX)
+ .put(LauncherSettings.Favorites.SPANY, item.spanY)
+ .put(LauncherSettings.Favorites.SCREEN, item.screenId);
- updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
+ updateItemInDatabaseHelper(context, writer, item, "modifyItemInDatabase");
}
/**
* Update an item to the database in a specified container.
*/
public static void updateItemInDatabase(Context context, final ItemInfo item) {
- final ContentValues values = new ContentValues();
- item.onAddToDatabase(context, values);
- updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
- }
-
- private void assertWorkspaceLoaded() {
- if (ProviderConfig.IS_DOGFOOD_BUILD) {
- synchronized (mLock) {
- if (!mHasLoaderCompletedOnce ||
- (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
- throw new RuntimeException("Trying to add shortcut while loader is running");
- }
- }
- }
- }
-
- /**
- * Returns true if the shortcuts already exists on the workspace. This must be called after
- * the workspace has been loaded. We identify a shortcut by its intent.
- */
- @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
- assertWorkspaceLoaded();
- final String intentWithPkg, intentWithoutPkg;
- if (intent.getComponent() != null) {
- // If component is not null, an intent with null package will produce
- // the same result and should also be a match.
- String packageName = intent.getComponent().getPackageName();
- if (intent.getPackage() != null) {
- intentWithPkg = intent.toUri(0);
- intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
- } else {
- intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
- intentWithoutPkg = intent.toUri(0);
- }
- } else {
- intentWithPkg = intent.toUri(0);
- intentWithoutPkg = intent.toUri(0);
- }
-
- synchronized (sBgLock) {
- for (ItemInfo item : sBgItemsIdMap) {
- if (item instanceof ShortcutInfo) {
- ShortcutInfo info = (ShortcutInfo) item;
- Intent targetIntent = info.promisedIntent == null
- ? info.intent : info.promisedIntent;
- if (targetIntent != null && info.user.equals(user)) {
- Intent copyIntent = new Intent(targetIntent);
- copyIntent.setSourceBounds(intent.getSourceBounds());
- String s = copyIntent.toUri(0);
- if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
- return true;
- }
- }
- }
- }
- }
- return false;
+ ContentWriter writer = new ContentWriter(context);
+ item.onAddToDatabase(writer);
+ updateItemInDatabaseHelper(context, writer, item, "updateItemInDatabase");
}
/**
@@ -895,90 +545,50 @@
item.screenId = screenId;
}
- final ContentValues values = new ContentValues();
+ final ContentWriter writer = new ContentWriter(context);
final ContentResolver cr = context.getContentResolver();
- item.onAddToDatabase(context, values);
+ item.onAddToDatabase(writer);
item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
- values.put(LauncherSettings.Favorites._ID, item.id);
+ writer.put(LauncherSettings.Favorites._ID, item.id);
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
Runnable r = new Runnable() {
public void run() {
- cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
+ cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
+ synchronized (sBgDataModel) {
checkItemInfoLocked(item.id, item, stackTrace);
- sBgItemsIdMap.put(item.id, item);
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- sBgFolders.put(item.id, (FolderInfo) item);
- // Fall through
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
- item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- sBgWorkspaceItems.add(item);
- } else {
- if (!sBgFolders.containsKey(item.container)) {
- // Adding an item to a folder that doesn't exist.
- String msg = "adding item: " + item + " to a folder that " +
- " doesn't exist";
- Log.e(TAG, msg);
- }
- }
- if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- incrementPinnedShortcutCount(
- ShortcutKey.fromShortcutInfo((ShortcutInfo) item),
- true /* shouldPin */);
- }
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- sBgAppWidgets.add((LauncherAppWidgetInfo) item);
- break;
- }
+ sBgDataModel.addItem(item, true);
}
}
};
runOnWorkerThread(r);
}
- private static ArrayList<ItemInfo> getItemsByPackageName(
- final String pn, final UserHandleCompat user) {
- ItemInfoFilter filter = new ItemInfoFilter() {
- @Override
- public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
- return cn.getPackageName().equals(pn) && info.user.equals(user);
- }
- };
- return filterItemInfos(sBgItemsIdMap, filter);
- }
-
- /**
- * Removes all the items from the database corresponding to the specified package.
- */
- static void deletePackageFromDatabase(Context context, final String pn,
- final UserHandleCompat user) {
- deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
- }
-
/**
* Removes the specified item from the database
*/
public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
- ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+ ArrayList<ItemInfo> items = new ArrayList<>();
items.add(item);
deleteItemsFromDatabase(context, items);
}
/**
+ * Removes all the items from the database matching {@param matcher}.
+ */
+ public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) {
+ deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap));
+ }
+
+ /**
* Removes the specified items from the database
*/
- static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
+ public static void deleteItemsFromDatabase(Context context,
+ final Iterable<? extends ItemInfo> items) {
final ContentResolver cr = context.getContentResolver();
Runnable r = new Runnable() {
public void run() {
@@ -986,36 +596,7 @@
final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
cr.delete(uri, null, null);
- // Lock on mBgLock *after* the db operation
- synchronized (sBgLock) {
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- sBgFolders.remove(item.id);
- for (ItemInfo info: sBgItemsIdMap) {
- if (info.container == item.id) {
- // We are deleting a folder which still contains items that
- // think they are contained by that folder.
- String msg = "deleting a folder (" + item + ") which still " +
- "contains items (" + info + ")";
- Log.e(TAG, msg);
- }
- }
- sBgWorkspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo(
- (ShortcutInfo) item));
- // Fall through.
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- sBgWorkspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
- break;
- }
- sBgItemsIdMap.remove(item.id);
- }
+ sBgDataModel.removeItem(item);
}
}
};
@@ -1023,43 +604,10 @@
}
/**
- * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
- */
- private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
- synchronized (sBgLock) {
- MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
- if (count == null || --count.value == 0) {
- LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
- }
- }
- }
-
- /**
- * Increment the count for the given shortcut, pinning it if the count becomes 1.
- *
- * As an optimization, the caller can pass shouldPin == false to avoid
- * unnecessary RPC's if the shortcut is already pinned.
- */
- private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
- synchronized (sBgLock) {
- MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
- if (count == null) {
- count = new MutableInt(1);
- sBgPinnedShortcutCounts.put(pinnedShortcut, count);
- } else {
- count.value++;
- }
- if (shouldPin && count.value == 1) {
- LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
- }
- }
- }
-
- /**
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
- public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1094,9 +642,9 @@
throw new RuntimeException(ex);
}
- synchronized (sBgLock) {
- sBgWorkspaceScreens.clear();
- sBgWorkspaceScreens.addAll(screensCopy);
+ synchronized (sBgDataModel) {
+ sBgDataModel.workspaceScreens.clear();
+ sBgDataModel.workspaceScreens.addAll(screensCopy);
}
}
};
@@ -1111,22 +659,13 @@
Runnable r = new Runnable() {
public void run() {
- 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);
- sBgWorkspaceItems.remove(info);
- }
-
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);
- }
- }
+ sBgDataModel.removeItem(info.contents);
+ info.contents.clear();
+
+ cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+ sBgDataModel.removeItem(info);
}
};
runOnWorkerThread(r);
@@ -1147,64 +686,62 @@
@Override
public void onPackageChanged(String packageName, UserHandleCompat user) {
int op = PackageUpdatedTask.OP_UPDATE;
- enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
@Override
public void onPackageRemoved(String packageName, UserHandleCompat user) {
+ onPackagesRemoved(user, packageName);
+ }
+
+ public void onPackagesRemoved(UserHandleCompat user, String... packages) {
int op = PackageUpdatedTask.OP_REMOVE;
- enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
}
@Override
public void onPackageAdded(String packageName, UserHandleCompat user) {
int op = PackageUpdatedTask.OP_ADD;
- enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
@Override
public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
boolean replacing) {
- enqueueItemUpdatedTask(
- new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
+ enqueueModelUpdateTask(
+ new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
}
@Override
public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
boolean replacing) {
if (!replacing) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
}
}
@Override
public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_SUSPEND, packageNames,
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_SUSPEND, user, packageNames));
}
@Override
public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_UNSUSPEND, packageNames,
- user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
}
@Override
public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
UserHandleCompat user) {
- enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
+ enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
}
public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
UserHandleCompat user) {
- enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+ enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
}
/**
@@ -1230,16 +767,15 @@
if (user != null) {
if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
- new String[0], user));
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
}
// ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
// we need to run the state change task again.
if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
- enqueueItemUpdatedTask(new UserLockStateChangedTask(user));
+ enqueueModelUpdateTask(new UserLockStateChangedTask(user));
}
}
} else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
@@ -1642,18 +1178,6 @@
}
}
- /** Clears all the sBg data structures */
- private void clearSBgDataStructures() {
- synchronized (sBgLock) {
- sBgWorkspaceItems.clear();
- sBgAppWidgets.clear();
- sBgFolders.clear();
- sBgItemsIdMap.clear();
- sBgWorkspaceScreens.clear();
- sBgPinnedShortcutCounts.clear();
- }
- }
-
private void loadWorkspace() {
if (LauncherAppState.PROFILE_STARTUP) {
Trace.beginSection("Loading Workspace");
@@ -1665,7 +1189,9 @@
final PackageManager manager = context.getPackageManager();
final boolean isSafeMode = manager.isSafeMode();
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
final boolean isSdCardReady = Utilities.isBootCompleted();
+ final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>();
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
@@ -1696,11 +1222,12 @@
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
- synchronized (sBgLock) {
- clearSBgDataStructures();
+ synchronized (sBgDataModel) {
+ sBgDataModel.clear();
+
final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
.getInstance(mContext).updateAndGetActiveSessionCache();
- sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
+ sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
final ArrayList<Long> itemsToRemove = new ArrayList<>();
final ArrayList<Long> restoredRows = new ArrayList<>();
@@ -1760,8 +1287,8 @@
// We can only query for shortcuts when the user is unlocked.
if (userUnlocked) {
List<ShortcutInfoCompat> pinnedShortcuts =
- mDeepShortcutManager.queryForPinnedShortcuts(null, user);
- if (mDeepShortcutManager.wasLastCallSuccess()) {
+ shortcutManager.queryForPinnedShortcuts(null, user);
+ if (shortcutManager.wasLastCallSuccess()) {
for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
@@ -1904,12 +1431,7 @@
// SdCard is not ready yet. Package might get available,
// once it is ready.
Log.d(TAG, "Invalid package: " + cn + " (check again later)");
- HashSet<String> pkgs = sPendingPackages.get(user);
- if (pkgs == null) {
- pkgs = new HashSet<String>();
- sPendingPackages.put(user, pkgs);
- }
- pkgs.add(cn.getPackageName());
+ pendingPackages.addToList(user, cn.getPackageName());
allowMissingTarget = true;
// Add the icon on the workspace anyway.
@@ -1980,7 +1502,6 @@
info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
}
- incrementPinnedShortcutCount(key, false /* shouldPin */);
} else { // item type == ITEM_TYPE_SHORTCUT
info = getShortcutInfo(c, cursorIconInfo);
@@ -2022,7 +1543,7 @@
}
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
+ if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) {
itemsToRemove.add(id);
break;
}
@@ -2039,19 +1560,7 @@
}
}
- switch (container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
- sBgWorkspaceItems.add(info);
- break;
- default:
- // Item is in a user folder
- FolderInfo folderInfo =
- findOrMakeFolder(sBgFolders, container);
- folderInfo.add(info, false);
- break;
- }
- sBgItemsIdMap.put(info.id, info);
+ sBgDataModel.addItem(info, false);
} else {
throw new RuntimeException("Unexpected null ShortcutInfo");
}
@@ -2059,7 +1568,7 @@
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
id = c.getLong(idIndex);
- FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+ FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(id);
// Do not trim the folder label, as is was set by the user.
folderInfo.title = c.getString(cursorIconInfo.titleIndex);
@@ -2073,25 +1582,16 @@
folderInfo.options = c.getInt(optionsIndex);
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
+ if (!checkItemPlacement(occupied, folderInfo, sBgDataModel.workspaceScreens)) {
itemsToRemove.add(id);
break;
}
-
- switch (container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
- sBgWorkspaceItems.add(folderInfo);
- break;
- }
-
if (restored) {
// no special handling required for restored folders
restoredRows.add(id);
}
- sBgItemsIdMap.put(folderInfo.id, folderInfo);
- sBgFolders.put(folderInfo.id, folderInfo);
+ sBgDataModel.addItem(folderInfo, false);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -2208,7 +1708,7 @@
appWidgetInfo.container = container;
// check & update map of what's occupied
- if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
+ if (!checkItemPlacement(occupied, appWidgetInfo, sBgDataModel.workspaceScreens)) {
itemsToRemove.add(id);
break;
}
@@ -2227,8 +1727,7 @@
updateItem(id, values);
}
}
- sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
- sBgAppWidgets.add(appWidgetInfo);
+ sBgDataModel.addItem(appWidgetInfo, false);
}
break;
}
@@ -2242,7 +1741,7 @@
// Break early if we've stopped loading
if (mStopped) {
- clearSBgDataStructures();
+ sBgDataModel.clear();
return;
}
@@ -2262,23 +1761,23 @@
LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
.getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
for (long folderId : deletedFolderIds) {
- sBgWorkspaceItems.remove(sBgFolders.get(folderId));
- sBgFolders.remove(folderId);
- sBgItemsIdMap.remove(folderId);
+ sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
+ sBgDataModel.folders.remove(folderId);
+ sBgDataModel.itemsIdMap.remove(folderId);
}
}
// Unpin shortcuts that don't exist on the workspace.
for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
- MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
+ MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
if (numTimesPinned == null || numTimesPinned.value == 0) {
// Shortcut is pinned but doesn't exist on the workspace; unpin it.
- mDeepShortcutManager.unpinShortcut(key);
+ shortcutManager.unpinShortcut(key);
}
}
// Sort all the folder items and make sure the first 3 items are high resolution.
- for (FolderInfo folder : sBgFolders) {
+ for (FolderInfo folder : sBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
int pos = 0;
for (ShortcutInfo info : folder.contents) {
@@ -2301,15 +1800,18 @@
LauncherSettings.Favorites._ID, restoredRows), null);
}
- if (!isSdCardReady && !sPendingPackages.isEmpty()) {
- context.registerReceiver(new AppsAvailabilityCheck(),
+ if (!isSdCardReady && !pendingPackages.isEmpty()) {
+ context.registerReceiver(
+ new SdCardAvailableReceiver(
+ LauncherModel.this, mContext, pendingPackages),
new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
- null, sWorker);
+ null,
+ sWorker);
}
// Remove any empty screens
- ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
- for (ItemInfo item: sBgItemsIdMap) {
+ ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgDataModel.workspaceScreens);
+ for (ItemInfo item: sBgDataModel.itemsIdMap) {
long screenId = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
unusedScreens.contains(screenId)) {
@@ -2319,8 +1821,8 @@
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
- sBgWorkspaceScreens.removeAll(unusedScreens);
- updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+ sBgDataModel.workspaceScreens.removeAll(unusedScreens);
+ updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
}
if (DEBUG_LOADERS) {
@@ -2533,10 +2035,10 @@
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
ArrayList<Long> orderedScreenIds = new ArrayList<>();
- synchronized (sBgLock) {
- workspaceItems.addAll(sBgWorkspaceItems);
- appWidgets.addAll(sBgAppWidgets);
- orderedScreenIds.addAll(sBgWorkspaceScreens);
+ synchronized (sBgDataModel) {
+ workspaceItems.addAll(sBgDataModel.workspaceItems);
+ appWidgets.addAll(sBgDataModel.appWidgets);
+ orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
}
final int currentScreen;
@@ -2680,8 +2182,8 @@
private void updateIconCache() {
// Ignore packages which have a promise icon.
HashSet<String> packagesToIgnore = new HashSet<>();
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
+ synchronized (sBgDataModel) {
+ for (ItemInfo info : sBgDataModel.itemsIdMap) {
if (info instanceof ShortcutInfo) {
ShortcutInfo si = (ShortcutInfo) info;
if (si.isPromise() && si.getTargetComponent() != null) {
@@ -2822,14 +2324,15 @@
Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
}
if (!mDeepShortcutsLoaded) {
- mBgDeepShortcutMap.clear();
- mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+ sBgDataModel.deepShortcutMap.clear();
+ DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
+ mHasShortcutHostPermission = shortcutManager.hasHostPermission();
if (mHasShortcutHostPermission) {
for (UserHandleCompat user : mUserManager.getUserProfiles()) {
if (mUserManager.isUserUnlocked(user)) {
- List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
- .queryForAllShortcuts(user);
- updateDeepShortcutMap(null, user, shortcuts);
+ List<ShortcutInfoCompat> shortcuts =
+ shortcutManager.queryForAllShortcuts(user);
+ sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
}
}
}
@@ -2844,45 +2347,18 @@
}
public void dumpState() {
- synchronized (sBgLock) {
+ synchronized (sBgDataModel) {
Log.d(TAG, "mLoaderTask.mContext=" + mContext);
Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
- Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
- }
- }
- }
-
- /**
- * Clear all the shortcuts for the given package, and re-add the new shortcuts.
- */
- private void updateDeepShortcutMap(
- String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
- if (packageName != null) {
- Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
- while (keysIter.hasNext()) {
- ComponentKey next = keysIter.next();
- if (next.componentName.getPackageName().equals(packageName)
- && next.user.equals(user)) {
- keysIter.remove();
- }
- }
- }
-
- // Now add the new shortcuts to the map.
- for (ShortcutInfoCompat shortcut : shortcuts) {
- boolean shouldShowInContainer = shortcut.isEnabled()
- && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
- if (shouldShowInContainer) {
- ComponentKey targetComponent
- = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
- mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+ Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size());
}
}
}
public void bindDeepShortcuts() {
- final MultiHashMap<ComponentKey, String> shortcutMapCopy = mBgDeepShortcutMap.clone();
+ final MultiHashMap<ComponentKey, String> shortcutMapCopy =
+ sBgDataModel.deepShortcutMap.clone();
Runnable r = new Runnable() {
@Override
public void run() {
@@ -2901,7 +2377,7 @@
* use partial updates similar to {@link UserManagerCompat}
*/
public void refreshShortcutsIfRequired() {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
sWorker.post(mShortcutPermissionCheckRunnable);
}
@@ -2911,441 +2387,68 @@
* Called when the icons for packages have been updated in the icon cache.
*/
public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
- final Callbacks callbacks = getCallback();
- final ArrayList<AppInfo> updatedApps = new ArrayList<>();
- final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-
// If any package icon has changed (app was updated while launcher was dead),
// update the corresponding shortcuts.
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- 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);
- updatedShortcuts.add(si);
- }
- }
- }
- mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
+ enqueueModelUpdateTask(new CacheDataUpdatedTask(
+ CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
+ }
+
+ void enqueueModelUpdateTask(BaseModelUpdateTask task) {
+ task.init(this);
+ runOnWorkerThread(task);
+ }
+
+ /**
+ * A task to be executed on the current callbacks on the UI thread.
+ * If there is no current callbacks, the task is ignored.
+ */
+ public interface CallbackTask {
+
+ void execute(Callbacks callbacks);
+ }
+
+ /**
+ * A runnable which changes/updates the data model of the launcher based on certain events.
+ */
+ public static abstract class BaseModelUpdateTask implements Runnable {
+
+ private LauncherModel mModel;
+ private DeferredHandler mUiHandler;
+
+ /* package private */
+ void init(LauncherModel model) {
+ mModel = model;
+ mUiHandler = mModel.mHandler;
}
- bindUpdatedShortcuts(updatedShortcuts, user);
-
- if (!updatedApps.isEmpty()) {
- mHandler.post(new Runnable() {
-
- public void run() {
- Callbacks cb = getCallback();
- if (cb != null && callbacks == cb) {
- cb.bindAppsUpdated(updatedApps);
- }
- }
- });
- }
- }
-
- private void bindUpdatedShortcuts(
- ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
- bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
- }
-
- private void bindUpdatedShortcuts(
- final ArrayList<ShortcutInfo> updatedShortcuts,
- final ArrayList<ShortcutInfo> removedShortcuts,
- final UserHandleCompat user) {
- if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
-
- public void run() {
- Callbacks cb = getCallback();
- if (cb != null && callbacks == cb) {
- cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
- }
- }
- });
- }
- }
-
- void enqueueItemUpdatedTask(Runnable task) {
- sWorker.post(task);
- }
-
- @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
-
@Override
- public void onReceive(Context context, Intent intent) {
- synchronized (sBgLock) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat
- .getInstance(mApp.getContext());
- final PackageManager manager = context.getPackageManager();
- final ArrayList<String> packagesRemoved = new ArrayList<String>();
- final ArrayList<String> packagesUnavailable = new ArrayList<String>();
- for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
- UserHandleCompat user = entry.getKey();
- packagesRemoved.clear();
- packagesUnavailable.clear();
- for (String pkg : entry.getValue()) {
- if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
- if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
- packagesUnavailable.add(pkg);
- } else {
- packagesRemoved.add(pkg);
- }
- }
- }
- if (!packagesRemoved.isEmpty()) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
- packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
- }
- if (!packagesUnavailable.isEmpty()) {
- enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
- packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
- }
- }
- sPendingPackages.clear();
- }
- }
- }
-
- private class PackageUpdatedTask implements Runnable {
- int mOp;
- String[] mPackages;
- UserHandleCompat mUser;
-
- public static final int OP_NONE = 0;
- public static final int OP_ADD = 1;
- public static final int OP_UPDATE = 2;
- public static final int OP_REMOVE = 3; // uninstlled
- public static final int OP_UNAVAILABLE = 4; // external media unmounted
- public static final int OP_SUSPEND = 5; // package suspended
- public static final int OP_UNSUSPEND = 6; // package unsuspended
- public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
-
- public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
- mOp = op;
- mPackages = packages;
- mUser = user;
- }
-
public void run() {
- if (!mHasLoaderCompletedOnce) {
+ if (!mModel.mHasLoaderCompletedOnce) {
// Loader has not yet run.
return;
}
- final Context context = mApp.getContext();
+ execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
+ }
- final String[] packages = mPackages;
- final int N = packages.length;
- FlagOp flagOp = FlagOp.NO_OP;
- StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
- switch (mOp) {
- case OP_ADD: {
- for (int i=0; i<N; i++) {
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
- mIconCache.updateIconsForPkg(packages[i], mUser);
- mBgAllAppsList.addPackage(context, packages[i], mUser);
- }
+ /**
+ * Execute the actual task. Called on the worker thread.
+ */
+ public abstract void execute(
+ LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
- 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);
- mApp.getWidgetCache().removePackage(packages[i], mUser);
- }
- // Since package was just updated, the target must be available now.
- flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
- break;
- 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:
- for (int i=0; i<N; i++) {
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
- mBgAllAppsList.removePackage(packages[i], mUser);
- mApp.getWidgetCache().removePackage(packages[i], mUser);
- }
- flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
- break;
- case OP_SUSPEND:
- case OP_UNSUSPEND:
- flagOp = mOp == OP_SUSPEND ?
- FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
- FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
- if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
- mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
- break;
- case OP_USER_AVAILABILITY_CHANGE:
- flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
- ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
- : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
- // We want to update all packages for this user.
- pkgFilter = StringFilter.matchesAll();
- mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
- break;
- }
-
- ArrayList<AppInfo> added = null;
- ArrayList<AppInfo> modified = null;
- final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
-
- if (mBgAllAppsList.added.size() > 0) {
- added = new ArrayList<>(mBgAllAppsList.added);
- mBgAllAppsList.added.clear();
- }
- if (mBgAllAppsList.modified.size() > 0) {
- modified = new ArrayList<>(mBgAllAppsList.modified);
- mBgAllAppsList.modified.clear();
- }
- if (mBgAllAppsList.removed.size() > 0) {
- removedApps.addAll(mBgAllAppsList.removed);
- mBgAllAppsList.removed.clear();
- }
-
- final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
-
- if (added != null) {
- addAppsToAllApps(context, added);
- for (AppInfo ai : added) {
- addedOrUpdatedApps.put(ai.componentName, ai);
- }
- }
-
- if (modified != null) {
- final Callbacks callbacks = getCallback();
- final ArrayList<AppInfo> modifiedFinal = modified;
- for (AppInfo ai : modified) {
- addedOrUpdatedApps.put(ai.componentName, ai);
- }
-
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAppsUpdated(modifiedFinal);
- }
- }
- });
- }
-
- // Update shortcut infos
- if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
- final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
- final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
- final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
-
- synchronized (sBgLock) {
- for (ItemInfo info : sBgItemsIdMap) {
- if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
- ShortcutInfo si = (ShortcutInfo) info;
- boolean infoUpdated = false;
- boolean shortcutUpdated = false;
-
- // Update shortcuts which use iconResource.
- if ((si.iconResource != null)
- && pkgFilter.matches(si.iconResource.packageName)) {
- Bitmap icon = Utilities.createIconBitmap(
- si.iconResource.packageName,
- si.iconResource.resourceName, context);
- if (icon != null) {
- si.setIcon(icon);
- si.usingFallbackIcon = false;
- infoUpdated = true;
- }
- }
-
- ComponentName cn = si.getTargetComponent();
- if (cn != null && pkgFilter.matches(cn.getPackageName())) {
- AppInfo appInfo = addedOrUpdatedApps.get(cn);
-
- if (si.isPromise()) {
- if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
- // Auto install icon
- PackageManager pm = context.getPackageManager();
- ResolveInfo matched = pm.resolveActivity(
- new Intent(Intent.ACTION_MAIN)
- .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
- PackageManager.MATCH_DEFAULT_ONLY);
- if (matched == null) {
- // Try to find the best match activity.
- Intent intent = pm.getLaunchIntentForPackage(
- cn.getPackageName());
- if (intent != null) {
- cn = intent.getComponent();
- appInfo = addedOrUpdatedApps.get(cn);
- }
-
- if ((intent == null) || (appInfo == null)) {
- removedShortcuts.add(si);
- continue;
- }
- si.promisedIntent = intent;
- }
- }
-
- // Restore the shortcut.
- if (appInfo != null) {
- si.flags = appInfo.flags;
- }
-
- si.intent = si.promisedIntent;
- si.promisedIntent = null;
- si.status = ShortcutInfo.DEFAULT;
- infoUpdated = true;
- si.updateIcon(mIconCache);
- }
-
- if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
- && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- si.updateIcon(mIconCache);
- si.title = Utilities.trim(appInfo.title);
- si.contentDescription = appInfo.contentDescription;
- infoUpdated = true;
- }
-
- int oldDisabledFlags = si.isDisabled;
- si.isDisabled = flagOp.apply(si.isDisabled);
- if (si.isDisabled != oldDisabledFlags) {
- shortcutUpdated = true;
- }
- }
-
- if (infoUpdated || shortcutUpdated) {
- updatedShortcuts.add(si);
- }
- if (infoUpdated) {
- updateItemInDatabase(context, si);
- }
- } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
- LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
- if (mUser.equals(widgetInfo.user)
- && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
- && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
- widgetInfo.restoreStatus &=
- ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
- ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
-
- // adding this flag ensures that launcher shows 'click to setup'
- // if the widget has a config activity. In case there is no config
- // activity, it will be marked as 'restored' during bind.
- widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
- widgets.add(widgetInfo);
- updateItemInDatabase(context, widgetInfo);
- }
- }
+ /**
+ * Schedules a {@param task} to be executed on the current callbacks.
+ */
+ public final void scheduleCallbackTask(final CallbackTask task) {
+ final Callbacks callbacks = mModel.getCallback();
+ mUiHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = mModel.getCallback();
+ if (callbacks == cb && cb != null) {
+ task.execute(callbacks);
}
}
-
- bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
- if (!removedShortcuts.isEmpty()) {
- deleteItemsFromDatabase(context, removedShortcuts);
- }
-
- if (!widgets.isEmpty()) {
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindWidgetsRestored(widgets);
- }
- }
- });
- }
- }
-
- final HashSet<String> removedPackages = new HashSet<>();
- final HashSet<ComponentName> removedComponents = new HashSet<>();
- if (mOp == OP_REMOVE) {
- // Mark all packages in the broadcast to be removed
- Collections.addAll(removedPackages, packages);
-
- // No need to update the removedComponents as
- // removedPackages is a super-set of removedComponents
- } else if (mOp == OP_UPDATE) {
- // Mark disabled packages in the broadcast to be removed
- for (int i=0; i<N; i++) {
- if (isPackageDisabled(context, packages[i], mUser)) {
- removedPackages.add(packages[i]);
- }
- }
-
- // Update removedComponents as some components can get removed during package update
- for (AppInfo info : removedApps) {
- removedComponents.add(info.componentName);
- }
- }
-
- if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
- for (String pn : removedPackages) {
- deletePackageFromDatabase(context, pn, mUser);
- }
- for (ComponentName cn : removedComponents) {
- deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
- }
-
- // Remove any queued items from the install queue
- InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
-
- // Call the components-removed callback
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindWorkspaceComponentsRemoved(
- removedPackages, removedComponents, mUser);
- }
- }
- });
- }
-
- if (!removedApps.isEmpty()) {
- // Remove corresponding apps from All-Apps
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAppInfosRemoved(removedApps);
- }
- }
- });
- }
-
- // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
- // get widget update signals.
- if (!Utilities.ATLEAST_MARSHMALLOW &&
- (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
- final Callbacks callbacks = getCallback();
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.notifyWidgetProvidersChanged();
- }
- }
- });
- }
+ });
}
}
@@ -3353,177 +2456,27 @@
* Repopulates the shortcut info, possibly updating any icon already on the workspace.
*/
public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
- enqueueItemUpdatedTask(new Runnable() {
+ enqueueModelUpdateTask(new ExtendedModelTask() {
@Override
- public void run() {
- info.updateFromDeepShortcutInfo(
- fullDetail, LauncherAppState.getInstance().getContext());
- ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ info.updateFromDeepShortcutInfo(fullDetail, app.getContext());
+
+ ArrayList<ShortcutInfo> update = new ArrayList<>();
update.add(info);
bindUpdatedShortcuts(update, fullDetail.getUserHandle());
}
});
}
- private class ShortcutsChangedTask implements Runnable {
- private final String mPackageName;
- private final List<ShortcutInfoCompat> mShortcuts;
- private final UserHandleCompat mUser;
- private final boolean mUpdateIdMap;
-
- public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
- UserHandleCompat user, boolean updateIdMap) {
- mPackageName = packageName;
- mShortcuts = shortcuts;
- mUser = user;
- mUpdateIdMap = updateIdMap;
- }
-
- @Override
- public void run() {
- mDeepShortcutManager.onShortcutsChanged(mShortcuts);
-
- // Find ShortcutInfo's that have changed on the workspace.
- final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
- MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
- for (ItemInfo itemInfo : sBgItemsIdMap) {
- if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- ShortcutInfo si = (ShortcutInfo) itemInfo;
- if (si.getPromisedIntent().getPackage().equals(mPackageName)
- && si.user.equals(mUser)) {
- idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
- }
- }
- }
-
- final Context context = LauncherAppState.getInstance().getContext();
- final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
- if (!idsToWorkspaceShortcutInfos.isEmpty()) {
- // Update the workspace to reflect the changes to updated shortcuts residing on it.
- List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
- mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
- for (ShortcutInfoCompat fullDetails : shortcuts) {
- List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
- .remove(fullDetails.getId());
- if (!fullDetails.isPinned()) {
- // The shortcut was previously pinned but is no longer, so remove it from
- // the workspace and our pinned shortcut counts.
- // Note that we put this check here, after querying for full details,
- // because there's a possible race condition between pinning and
- // receiving this callback.
- removedShortcutInfos.addAll(shortcutInfos);
- continue;
- }
- for (ShortcutInfo shortcutInfo : shortcutInfos) {
- shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
- updatedShortcutInfos.add(shortcutInfo);
- }
- }
- }
-
- // If there are still entries in idsToWorkspaceShortcutInfos, that means that
- // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
- // means they were cleared, so we remove and unpin them now.
- for (String id : idsToWorkspaceShortcutInfos.keySet()) {
- removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
- }
-
- bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
- if (!removedShortcutInfos.isEmpty()) {
- deleteItemsFromDatabase(context, removedShortcutInfos);
- }
-
- if (mUpdateIdMap) {
- // Update the deep shortcut map if the list of ids has changed for an activity.
- updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
- bindDeepShortcuts();
- }
- }
- }
-
- /**
- * Task to handle changing of lock state of the user
- */
- private class UserLockStateChangedTask implements Runnable {
-
- private final UserHandleCompat mUser;
-
- public UserLockStateChangedTask(UserHandleCompat user) {
- mUser = user;
- }
-
- @Override
- public void run() {
- boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser);
- Context context = mApp.getContext();
-
- HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
- if (isUserUnlocked) {
- List<ShortcutInfoCompat> shortcuts =
- mDeepShortcutManager.queryForPinnedShortcuts(null, mUser);
- if (mDeepShortcutManager.wasLastCallSuccess()) {
- for (ShortcutInfoCompat shortcut : shortcuts) {
- pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
- }
- } else {
- // Shortcut manager can fail due to some race condition when the lock state
- // changes too frequently. For the purpose of the update,
- // consider it as still locked.
- isUserUnlocked = false;
- }
- }
-
- // Update the workspace to reflect the changes to updated shortcuts residing on it.
- ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
- ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
- for (ItemInfo itemInfo : sBgItemsIdMap) {
- if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
- && mUser.equals(itemInfo.user)) {
- ShortcutInfo si = (ShortcutInfo) itemInfo;
- if (isUserUnlocked) {
- ShortcutInfoCompat shortcut =
- pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
- // We couldn't verify the shortcut during loader. If its no longer available
- // (probably due to clear data), delete the workspace item as well
- if (shortcut == null) {
- deletedShortcutInfos.add(si);
- continue;
- }
- si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
- si.updateFromDeepShortcutInfo(shortcut, context);
- } else {
- si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
- }
- updatedShortcutInfos.add(si);
- }
- }
- bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
- if (!deletedShortcutInfos.isEmpty()) {
- deleteItemsFromDatabase(context, deletedShortcutInfos);
- }
-
- // Remove shortcut id map for that user
- Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
- while (keysIter.hasNext()) {
- if (keysIter.next().user.equals(mUser)) {
- keysIter.remove();
- }
- }
-
- if (isUserUnlocked) {
- updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
- }
- bindDeepShortcuts();
- }
- }
-
- private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
+ private void bindWidgetsModel(final Callbacks callbacks) {
+ final MultiHashMap<PackageItemInfo, WidgetItem> widgets
+ = mBgWidgetsModel.getWidgetsMap().clone();
mHandler.post(new Runnable() {
@Override
public void run() {
Callbacks cb = getCallback();
if (callbacks == cb && cb != null) {
- callbacks.bindWidgetsModel(model);
+ callbacks.bindAllWidgets(widgets);
}
}
});
@@ -3535,44 +2488,17 @@
@Override
public void run() {
if (bindFirst && !mBgWidgetsModel.isEmpty()) {
- bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
+ bindWidgetsModel(callbacks);
}
- final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
- bindWidgetsModel(callbacks, model);
+ ArrayList<WidgetItem> allWidgets = mBgWidgetsModel.update(mApp.getContext());
+ bindWidgetsModel(callbacks);
+
// update the Widget entries inside DB on the worker thread.
- LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
- model.getRawList());
+ LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(allWidgets);
}
});
}
- @Thunk static boolean isPackageDisabled(Context context, String packageName,
- UserHandleCompat user) {
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- return !launcherApps.isPackageEnabledForProfile(packageName, user);
- }
-
- public static boolean isValidPackageActivity(Context context, ComponentName cn,
- UserHandleCompat user) {
- if (cn == null) {
- return false;
- }
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
- return false;
- }
- return launcherApps.isActivityEnabledForProfile(cn, user);
- }
-
- public static boolean isValidPackage(Context context, String packageName,
- UserHandleCompat user) {
- if (packageName == null) {
- return false;
- }
- final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- return launcherApps.isPackageEnabledForProfile(packageName, user);
- }
-
/**
* Make an ShortcutInfo object for a restored application or shortcut item that points
* to a package that is not yet installed on the system.
@@ -3680,56 +2606,9 @@
info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
info.user = user;
info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
- if (lai != null) {
- info.flags = AppInfo.initFlags(lai);
- }
return info;
}
- static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
- ItemInfoFilter f) {
- HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
- for (ItemInfo i : infos) {
- if (i instanceof ShortcutInfo) {
- ShortcutInfo info = (ShortcutInfo) i;
- ComponentName cn = info.getTargetComponent();
- if (cn != null && f.filterItem(null, info, cn)) {
- filtered.add(info);
- }
- } else if (i instanceof FolderInfo) {
- FolderInfo info = (FolderInfo) i;
- for (ShortcutInfo s : info.contents) {
- ComponentName cn = s.getTargetComponent();
- if (cn != null && f.filterItem(info, s, cn)) {
- filtered.add(s);
- }
- }
- } else if (i instanceof LauncherAppWidgetInfo) {
- LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
- ComponentName cn = info.providerName;
- if (cn != null && f.filterItem(null, info, cn)) {
- filtered.add(info);
- }
- }
- }
- return new ArrayList<ItemInfo>(filtered);
- }
-
- @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
- final UserHandleCompat user) {
- ItemInfoFilter filter = new ItemInfoFilter() {
- @Override
- public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
- if (info.user == null) {
- return cn.equals(cname);
- } else {
- return cn.equals(cname) && info.user.equals(user);
- }
- }
- };
- return filterItemInfos(sBgItemsIdMap, filter);
- }
-
/**
* Make an ShortcutInfo object for a shortcut that isn't an application.
*/
@@ -3754,7 +2633,6 @@
// the fallback icon
if (icon == null) {
icon = mIconCache.getDefaultIcon(info.user);
- info.usingFallbackIcon = true;
}
info.setIcon(icon);
}
@@ -3771,17 +2649,15 @@
}
Bitmap icon = null;
- boolean customIcon = false;
ShortcutIconResource iconResource = null;
if (bitmap instanceof Bitmap) {
- icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
- customIcon = true;
+ icon = LauncherIcons.createIconBitmap((Bitmap) bitmap, context);
} else {
Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
if (extra instanceof ShortcutIconResource) {
iconResource = (ShortcutIconResource) extra;
- icon = Utilities.createIconBitmap(iconResource.packageName,
+ icon = LauncherIcons.createIconBitmap(iconResource.packageName,
iconResource.resourceName, context);
}
}
@@ -3793,7 +2669,6 @@
info.user = UserHandleCompat.myUserHandle();
if (icon == null) {
icon = mIconCache.getDefaultIcon(info.user);
- info.usingFallbackIcon = true;
}
info.setIcon(icon);
@@ -3805,22 +2680,6 @@
return info;
}
- /**
- * Return an existing FolderInfo object if we have encountered this ID previously,
- * or make a new one.
- */
- @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
- // See if a placeholder was created for us already
- FolderInfo folderInfo = folders.get(id);
- if (folderInfo == null) {
- // No placeholder -- create a new instance
- folderInfo = new FolderInfo();
- folders.put(id, folderInfo);
- }
- return folderInfo;
- }
-
-
static boolean isValidProvider(AppWidgetProviderInfo provider) {
return (provider != null) && (provider.provider != null)
&& (provider.provider.getPackageName() != null);
@@ -3847,8 +2706,8 @@
* @return {@link FolderInfo} if its already loaded.
*/
public FolderInfo findFolderById(Long folderId) {
- synchronized (sBgLock) {
- return sBgFolders.get(folderId);
+ synchronized (sBgDataModel) {
+ return sBgDataModel.folders.get(folderId);
}
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index f3d9493..349f094 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -649,11 +649,8 @@
// Database was just created, so wipe any previous widgets
if (mWidgetHostResetHandler != null) {
new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID).deleteHost();
- mWidgetHostResetHandler.sendMessage(Message.obtain(
- mWidgetHostResetHandler,
- ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET,
- mContext
- ));
+ mWidgetHostResetHandler.sendEmptyMessage(
+ ChangeListenerWrapper.MSG_EXTRACTED_COLORS_CHANGED);
}
// Set the flag for empty DB
@@ -764,12 +761,7 @@
}
}
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.markFirstRunClingDismissed(mContext);
+ // No-op
}
case 17: {
// No-op
@@ -1019,7 +1011,7 @@
public void checkId(String table, ContentValues values) {
long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
- if (table == WorkspaceScreens.TABLE_NAME) {
+ if (WorkspaceScreens.TABLE_NAME.equals(table)) {
mMaxScreenId = Math.max(id, mMaxScreenId);
} else {
mMaxItemId = Math.max(id, mMaxItemId);
@@ -1141,17 +1133,13 @@
if (mListener != null) {
switch (msg.what) {
case MSG_LAUNCHER_PROVIDER_CHANGED:
- mListener.onLauncherProviderChange();
+ mListener.onLauncherProviderChanged();
break;
case MSG_EXTRACTED_COLORS_CHANGED:
mListener.onExtractedColorsChanged();
break;
case MSG_APP_WIDGET_HOST_RESET:
- Context context = (Context) msg.obj;
- if (context != null) {
- context.sendBroadcast(new Intent(Launcher.ACTION_APPWIDGET_HOST_RESET)
- .setPackage(context.getPackageName()));
- }
+ mListener.onAppWidgetHostReset();
break;
}
}
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
index 5998dad..7044812 100644
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ b/src/com/android/launcher3/LauncherProviderChangeListener.java
@@ -7,7 +7,9 @@
*/
public interface LauncherProviderChangeListener {
- public void onLauncherProviderChange();
+ void onLauncherProviderChanged();
- public void onExtractedColorsChanged();
+ void onExtractedColorsChanged();
+
+ void onAppWidgetHostReset();
}
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index eb70650..19cc0fb 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,24 +23,23 @@
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.CircleRevealOutlineProvider;
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
*
@@ -119,9 +118,6 @@
public static final String TAG = "LSTAnimation";
- // 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;
@@ -139,7 +135,7 @@
* @param startSearchAfterTransition Immediately starts app search after the transition to
* All Apps is completed.
*/
- public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+ public void startAnimationToAllApps(
final boolean animated, final boolean startSearchAfterTransition) {
final AllAppsContainerView toView = mLauncher.getAppsView();
final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
@@ -174,18 +170,17 @@
animType = PULLUP;
}
// Only animate the search bar if animating from spring loaded mode back to all apps
- startAnimationToOverlay(fromWorkspaceState,
+ startAnimationToOverlay(
Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
}
/**
* Starts an animation to the widgets view.
*/
- public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
- final boolean animated) {
+ public void startAnimationToWidgets(final boolean animated) {
final WidgetsContainerView toView = mLauncher.getWidgetsView();
final View buttonView = mLauncher.getWidgetsButton();
- startAnimationToOverlay(fromWorkspaceState,
+ startAnimationToOverlay(
Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
@Override
@@ -228,9 +223,8 @@
/**
* Creates and starts a new animation to a particular overlay view.
*/
- @SuppressLint("NewApi")
private void startAnimationToOverlay(
- final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+ final Workspace.State toWorkspaceState,
final View buttonView, final BaseContainerView toView,
final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
@@ -241,9 +235,7 @@
final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
- final View fromView = mLauncher.getWorkspace();
-
- final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
// If for some reason our views aren't initialized, don't animate
boolean initialized = buttonView != null;
@@ -252,7 +244,7 @@
cancelAnimation();
final View contentView = toView.getContentView();
- playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+ playCommonTransitionAnimations(toWorkspaceState,
animated, initialized, animation, layerViews);
if (!animated || !initialized) {
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
@@ -268,13 +260,6 @@
// Show the content view
contentView.setVisibility(View.VISIBLE);
-
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
pCb.onTransitionComplete();
return;
}
@@ -319,14 +304,14 @@
panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
// Play the animation
- layerViews.put(revealView, BUILD_AND_SET_LAYER);
+ layerViews.addView(revealView);
animation.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);
+ layerViews.addView(contentView);
// Create the individual animators
ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
@@ -359,19 +344,9 @@
animation.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);
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
// This can hold unnecessary references to views.
cleanupAnimation();
pCb.onTransitionComplete();
@@ -379,92 +354,28 @@
});
- // Dispatch the prepare transition signal
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != 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.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
-
- // Focus the new view
- toView.requestFocus();
-
- stateAnimation.start();
- }
- };
toView.bringToFront();
toView.setVisibility(View.VISIBLE);
- toView.post(startAnimRunnable);
+
+ animation.addListener(layerViews);
+ toView.post(new StartAnimRunnable(animation, toView));
mCurrentAnimation = animation;
} else if (animType == PULLUP) {
// We are animating the content view alpha, so ensure we have a layer for it
- layerViews.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
-
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
cleanupAnimation();
pCb.onTransitionComplete();
}
});
boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
- dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionPrepare(toView, animated, false);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != 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.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
-
- toView.requestFocus();
- stateAnimation.start();
- }
- };
+ Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
mCurrentAnimation = animation;
+ mCurrentAnimation.addListener(layerViews);
if (shouldPost) {
toView.post(startAnimRunnable);
} else {
@@ -477,9 +388,9 @@
* Plays animations used by various transitions.
*/
private void playCommonTransitionAnimations(
- Workspace.State toWorkspaceState, View fromView, View toView,
+ Workspace.State toWorkspaceState,
boolean animated, boolean initialized, AnimatorSet animation,
- HashMap<View, Integer> layerViews) {
+ AnimationLayerSet layerViews) {
// Create the workspace animation.
// NOTE: this call apparently also sets the state for the workspace if !animated
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
@@ -490,36 +401,16 @@
if (workspaceAnim != null) {
animation.play(workspaceAnim);
}
- // Dispatch onLauncherTransitionStep() as the animation interpolates.
- animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
}
}
/**
- * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
- * {@param fromView} and {@param toView} as the animation interpolates.
- *
- * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
- */
- private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
- ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
- updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
- dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
- }
- });
- return updateAnimator;
- }
-
- /**
* Starts an animation to the workspace from the apps view.
*/
private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
final Workspace.State toWorkspaceState, final boolean animated, int type,
final Runnable onCompleteRunnable) {
- AllAppsContainerView appsView = mLauncher.getAppsView();
+ final AllAppsContainerView appsView = mLauncher.getAppsView();
// No alpha anim from all apps
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
@Override
@@ -549,6 +440,7 @@
@Override
void onTransitionComplete() {
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ appsView.reset();
}
};
// Only animate the search bar if animating to spring loaded mode from all apps
@@ -594,73 +486,32 @@
final Workspace.State toWorkspaceState, final boolean animated,
final Runnable onCompleteRunnable) {
final View fromWorkspace = mLauncher.getWorkspace();
- final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
- final int revealDuration = mLauncher.getResources()
- .getInteger(R.integer.config_overlayRevealTime);
// Cancel the current animation
cancelAnimation();
- boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
-
- playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
- animated, animated, animation, layerViews);
+ playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
if (animated) {
- dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != stateAnimation)
- return;
-
- dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
-
- // 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.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
- stateAnimation.start();
- }
- };
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
-
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
// This can hold unnecessary references to views.
cleanupAnimation();
}
});
- fromWorkspace.post(startAnimRunnable);
+ animation.addListener(layerViews);
+ fromWorkspace.post(new StartAnimRunnable(animation, null));
mCurrentAnimation = animation;
} else /* if (!animated) */ {
- dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
- dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
-
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
@@ -690,7 +541,7 @@
final View revealView = fromView.getRevealView();
final View contentView = fromView.getContentView();
- final HashMap<View, Integer> layerViews = new HashMap<>();
+ final AnimationLayerSet layerViews = new AnimationLayerSet();
// If for some reason our views aren't initialized, don't animate
boolean initialized = buttonView != null;
@@ -698,9 +549,7 @@
// Cancel the current animation
cancelAnimation();
- boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
-
- playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+ playCommonTransitionAnimations(toWorkspaceState,
animated, initialized, animation, layerViews);
if (!animated || !initialized) {
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
@@ -708,12 +557,6 @@
mAllAppsController.finishPullDown();
}
fromView.setVisibility(View.GONE);
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(toView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
pCb.onTransitionComplete();
// Run any queued runnables
@@ -733,7 +576,7 @@
revealView.setVisibility(View.VISIBLE);
revealView.setAlpha(1f);
revealView.setTranslationY(0);
- layerViews.put(revealView, BUILD_AND_SET_LAYER);
+ layerViews.addView(revealView);
// Calculate the final animation values
final float revealViewToXDrift;
@@ -781,7 +624,7 @@
}
// Setup the animation for the content view
- layerViews.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
// Create the individual animators
ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
@@ -826,28 +669,15 @@
}
}
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-
animation.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();
}
- // 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);
@@ -861,35 +691,12 @@
}
});
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != 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.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
- stateAnimation.start();
- }
- };
mCurrentAnimation = animation;
- fromView.post(startAnimRunnable);
+ mCurrentAnimation.addListener(layerViews);
+ fromView.post(new StartAnimRunnable(animation, null));
} else if (animType == PULLUP) {
// We are animating the content view alpha, so ensure we have a layer for it
- layerViews.put(contentView, BUILD_AND_SET_LAYER);
+ layerViews.addView(contentView);
animation.addListener(new AnimatorListenerAdapter() {
boolean canceled = false;
@@ -901,20 +708,11 @@
@Override
public void onAnimationEnd(Animator animation) {
if (canceled) return;
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
// Run any queued runnables
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
- // Disable all necessary layers
- for (View v : layerViews.keySet()) {
- if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
- v.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
cleanupAnimation();
pCb.onTransitionComplete();
}
@@ -922,37 +720,9 @@
});
boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
- // Dispatch the prepare transition signal
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-
- final AnimatorSet stateAnimation = animation;
- final Runnable startAnimRunnable = new Runnable() {
- public void run() {
- // Check that mCurrentAnimation hasn't changed while
- // we waited for a layout/draw pass
- if (mCurrentAnimation != 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.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
- v.buildLayer();
- }
- }
-
- // Focus the new view
- toView.requestFocus();
- stateAnimation.start();
- }
- };
+ Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
mCurrentAnimation = animation;
+ mCurrentAnimation.addListener(layerViews);
if (shouldPost) {
fromView.post(startAnimRunnable);
} else {
@@ -963,52 +733,6 @@
}
/**
- * Dispatches the prepare-transition event to suitable views.
- */
- void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
- boolean multiplePagesVisible) {
- if (v instanceof LauncherTransitionable) {
- ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
- multiplePagesVisible);
- }
- }
-
- /**
- * 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() {
@@ -1022,4 +746,26 @@
@Thunk void cleanupAnimation() {
mCurrentAnimation = null;
}
+
+ private class StartAnimRunnable implements Runnable {
+
+ private final AnimatorSet mAnim;
+ private final View mViewToFocus;
+
+ public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
+ mAnim = anim;
+ mViewToFocus = viewToFocus;
+ }
+
+ @Override
+ public void run() {
+ if (mCurrentAnimation != mAnim) {
+ return;
+ }
+ if (mViewToFocus != null) {
+ mViewToFocus.requestFocus();
+ }
+ mAnim.start();
+ }
+ }
}
diff --git a/src/com/android/launcher3/LauncherTransitionable.java b/src/com/android/launcher3/LauncherTransitionable.java
deleted file mode 100644
index b97aaec..0000000
--- a/src/com/android/launcher3/LauncherTransitionable.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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;
-
-/**
- * An interface to get callbacks during a launcher transition.
- */
-public interface LauncherTransitionable {
- void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean multiplePagesVisible);
- void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
- void onLauncherTransitionStep(Launcher l, float t);
- void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
-}
diff --git a/src/com/android/launcher3/OverviewButtonClickListener.java b/src/com/android/launcher3/OverviewButtonClickListener.java
new file mode 100644
index 0000000..c98f1d7
--- /dev/null
+++ b/src/com/android/launcher3/OverviewButtonClickListener.java
@@ -0,0 +1,51 @@
+package com.android.launcher3;
+
+import android.view.View;
+
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * A specialized listener for Overview buttons where both clicks and long clicks are logged
+ * handled the same via {@link #handleViewClick(View)}.
+ */
+public abstract class OverviewButtonClickListener implements View.OnClickListener,
+ View.OnLongClickListener {
+
+ private int mControlType; /** ControlType enum as defined in {@link LauncherLogProto} */
+
+ public OverviewButtonClickListener(int controlType) {
+ mControlType = controlType;
+ }
+
+ public void attachTo(View v) {
+ v.setOnClickListener(this);
+ v.setOnLongClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (shouldPerformClick(view)) {
+ handleViewClick(view, LauncherLogProto.Action.TAP);
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (shouldPerformClick(view)) {
+ handleViewClick(view, LauncherLogProto.Action.LONGPRESS);
+ }
+ return true;
+ }
+
+ private boolean shouldPerformClick(View view) {
+ return !Launcher.getLauncher(view.getContext()).getWorkspace().isSwitchingState();
+ }
+
+ private void handleViewClick(View view, int action) {
+ handleViewClick(view);
+ Launcher.getLauncher(view.getContext()).getUserEventDispatcher()
+ .logActionOnControl(action, mControlType);
+ }
+
+ public abstract void handleViewClick(View view);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e380e26..ee7f9f8 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -28,7 +28,6 @@
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -98,7 +97,6 @@
@ViewDebug.ExportedProperty(category = "launcher")
protected int mCurrentPage;
- protected int mRestorePage = INVALID_RESTORE_PAGE;
private int mChildCountOnLastLayout;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -119,7 +117,6 @@
private float mLastMotionXRemainder;
private float mLastMotionY;
private float mTotalMotionX;
- private int mLastScreenCenter = -1;
private boolean mCancelTap;
@@ -132,7 +129,6 @@
protected final static int TOUCH_STATE_REORDERING = 4;
protected int mTouchState = TOUCH_STATE_REST;
- private boolean mForceScreenScrolled = false;
protected OnLongClickListener mLongClickListener;
@@ -145,7 +141,7 @@
protected int mActivePointerId = INVALID_POINTER;
- protected boolean mIsPageMoving = false;
+ protected boolean mIsPageInTransition = false;
protected boolean mWasInOverscroll = false;
@@ -184,7 +180,6 @@
private static final float[] sTmpPoint = new float[2];
private static final int[] sTmpIntPoint = new int[2];
private static final Rect sTmpRect = new Rect();
- private static final RectF sTmpRectF = new RectF();
protected final Rect mInsets = new Rect();
protected final boolean mIsRtl;
@@ -410,7 +405,6 @@
if (getChildCount() == 0) {
return;
}
- mForceScreenScrolled = true;
mCurrentPage = validateNewPage(currentPage);
updateCurrentPageScroll();
notifyPageSwitchListener();
@@ -418,17 +412,6 @@
}
/**
- * The restore page will be set in place of the current page at the next (likely first)
- * layout.
- */
- void setRestorePage(int restorePage) {
- mRestorePage = restorePage;
- }
- int getRestorePage() {
- return mRestorePage;
- }
-
- /**
* Should be called whenever the page changes. In the case of a scroll, we wait until the page
* has settled.
*/
@@ -445,30 +428,36 @@
}
}
}
- protected void pageBeginMoving() {
- if (!mIsPageMoving) {
- mIsPageMoving = true;
- onPageBeginMoving();
+ protected void pageBeginTransition() {
+ if (!mIsPageInTransition) {
+ mIsPageInTransition = true;
+ onPageBeginTransition();
}
}
- protected void pageEndMoving() {
- if (mIsPageMoving) {
- mIsPageMoving = false;
- onPageEndMoving();
+ protected void pageEndTransition() {
+ if (mIsPageInTransition) {
+ mIsPageInTransition = false;
+ onPageEndTransition();
}
}
- protected boolean isPageMoving() {
- return mIsPageMoving;
+ protected boolean isPageInTransition() {
+ return mIsPageInTransition;
}
- // a method that subclasses can override to add behavior
- protected void onPageBeginMoving() {
+ /**
+ * Called when the page starts moving as part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onPageBeginTransition() {
}
- // a method that subclasses can override to add behavior
- protected void onPageEndMoving() {
+ /**
+ * Called when the page ends moving as part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onPageEndTransition() {
mWasInOverscroll = false;
}
@@ -596,7 +585,7 @@
// We don't want to trigger a page end moving unless the page has settled
// and the user has stopped scrolling
if (mTouchState == TOUCH_STATE_REST) {
- pageEndMoving();
+ pageEndTransition();
}
onPostReorderingAnimationCompleted();
@@ -879,12 +868,7 @@
}
if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
- if (mRestorePage != INVALID_RESTORE_PAGE) {
- setCurrentPage(mRestorePage);
- mRestorePage = INVALID_RESTORE_PAGE;
- } else {
- setCurrentPage(getNextPage());
- }
+ setCurrentPage(getNextPage());
}
mChildCountOnLastLayout = childCount;
@@ -916,11 +900,6 @@
requestLayout();
}
- /**
- * Called when the center screen changes during scrolling.
- */
- protected void screenScrolled(int screenCenter) { }
-
@Override
public void onChildViewAdded(View parent, View child) {
// Update the page indicator, we don't update the page indicator as we
@@ -931,14 +910,12 @@
// This ensures that when children are added, they get the correct transforms / alphas
// in accordance with any scroll effects.
- mForceScreenScrolled = true;
updateFreescrollBounds();
invalidate();
}
@Override
public void onChildViewRemoved(View parent, View child) {
- mForceScreenScrolled = true;
updateFreescrollBounds();
invalidate();
}
@@ -996,99 +973,6 @@
range[1] = Math.max(0, getChildCount() - 1);
}
- protected void getVisiblePages(int[] range) {
- final int count = getChildCount();
- range[0] = -1;
- range[1] = -1;
-
- if (count > 0) {
- final int visibleLeft = -getLeft();
- final int visibleRight = visibleLeft + getViewportWidth();
- final Matrix pageShiftMatrix = getPageShiftMatrix();
- int curScreen = 0;
-
- for (int i = 0; i < count; i++) {
- View currPage = getPageAt(i);
-
- // Verify if the page bounds are within the visible range.
- sTmpRectF.left = 0;
- sTmpRectF.right = currPage.getMeasuredWidth();
- currPage.getMatrix().mapRect(sTmpRectF);
- sTmpRectF.offset(currPage.getLeft() - getScrollX(), 0);
- pageShiftMatrix.mapRect(sTmpRectF);
-
- if (sTmpRectF.left > visibleRight || sTmpRectF.right < visibleLeft) {
- if (range[0] == -1) {
- continue;
- } else {
- break;
- }
- }
- curScreen = i;
- if (range[0] < 0) {
- range[0] = curScreen;
- }
- }
-
- range[1] = curScreen;
- } else {
- range[0] = -1;
- range[1] = -1;
- }
- }
-
- protected Matrix getPageShiftMatrix() {
- return getMatrix();
- }
-
- protected boolean shouldDrawChild(View child) {
- return child.getVisibility() == VISIBLE;
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- // Find out which screens are visible; as an optimization we only call draw on them
- final int pageCount = getChildCount();
- if (pageCount > 0) {
- int halfScreenSize = getViewportWidth() / 2;
- int screenCenter = getScrollX() + halfScreenSize;
-
- if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
- // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
- // set it for the next frame
- mForceScreenScrolled = false;
- screenScrolled(screenCenter);
- mLastScreenCenter = screenCenter;
- }
-
- getVisiblePages(mTempVisiblePagesRange);
- final int leftScreen = mTempVisiblePagesRange[0];
- final int rightScreen = mTempVisiblePagesRange[1];
- if (leftScreen != -1 && rightScreen != -1) {
- final long drawingTime = getDrawingTime();
- // Clip to the bounds
- canvas.save();
- canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
- getScrollY() + getBottom() - getTop());
-
- // Draw all the children, leaving the drag view for last
- for (int i = pageCount - 1; i >= 0; i--) {
- final View v = getPageAt(i);
- if (v == mDragView) continue;
- if (leftScreen <= i && i <= rightScreen && shouldDrawChild(v)) {
- drawChild(canvas, v, drawingTime);
- }
- }
- // Draw the drag view on top (if there is one)
- if (mDragView != null) {
- drawChild(canvas, mDragView, drawingTime);
- }
-
- canvas.restore();
- }
- }
- }
-
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -1099,7 +983,7 @@
canvas.translate(display.left, display.top);
canvas.rotate(270);
- getEdgeVerticalPostion(sTmpIntPoint);
+ getEdgeVerticalPosition(sTmpIntPoint);
canvas.translate(display.top - sTmpIntPoint[1], 0);
mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
if (mEdgeGlowLeft.draw(canvas)) {
@@ -1113,7 +997,7 @@
canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
canvas.rotate(90);
- getEdgeVerticalPostion(sTmpIntPoint);
+ getEdgeVerticalPosition(sTmpIntPoint);
canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
@@ -1128,7 +1012,7 @@
/**
* Returns the top and bottom position for the edge effect.
*/
- protected abstract void getEdgeVerticalPostion(int[] pos);
+ protected abstract void getEdgeVerticalPosition(int[] pos);
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
@@ -1184,6 +1068,10 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+ return;
+ }
+
// XXX-RTL: This will be fixed in a future CL
if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
@@ -1338,7 +1226,7 @@
mTouchState = TOUCH_STATE_REST;
if (!mScroller.isFinished() && !mFreeScroll) {
setCurrentPage(getNextPage());
- pageEndMoving();
+ pageEndTransition();
}
} else {
if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
@@ -1399,7 +1287,7 @@
mLastMotionX = x;
mLastMotionXRemainder = 0;
onScrollInteractionBegin();
- pageBeginMoving();
+ pageBeginTransition();
// Stop listening for things like pinches.
requestDisallowInterceptTouchEvent(true);
}
@@ -1581,7 +1469,7 @@
if (mTouchState == TOUCH_STATE_SCROLLING) {
onScrollInteractionBegin();
- pageBeginMoving();
+ pageBeginTransition();
}
break;
@@ -2027,7 +1915,6 @@
mNextPage = whichPage;
- pageBeginMoving();
awakenScrollBars(duration);
if (immediate) {
duration = 0;
@@ -2035,6 +1922,10 @@
duration = Math.abs(delta);
}
+ if (duration != 0) {
+ pageBeginTransition();
+ }
+
if (!mScroller.isFinished()) {
abortScrollerAnimation(false);
}
@@ -2054,7 +1945,6 @@
computeScroll();
}
- mForceScreenScrolled = true;
invalidate();
}
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index 31820d7..76de3e7 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -20,7 +20,7 @@
/**
* 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
+ * e.g., this object is used to pass information on draggable targets when they are dropped onto
* the workspace from another container.
*/
public class PendingAddItemInfo extends ItemInfo {
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index f01c7f2..bf39774 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -134,7 +134,7 @@
// 3) Setup icon in the center and app icon in the top right corner.
if (mDisabledForSafeMode) {
FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
- disabledIcon.setState(FastBitmapDrawable.State.DISABLED);
+ disabledIcon.setIsDisabled(true);
mCenterDrawable = disabledIcon;
mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index baeb77c..c1d60fd 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -24,6 +24,8 @@
import android.view.View;
import android.view.animation.LinearInterpolator;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
import static com.android.launcher3.Workspace.State.NORMAL;
import static com.android.launcher3.Workspace.State.OVERVIEW;
@@ -58,7 +60,6 @@
private final Animator[] mAnimators = new Animator[4];
- private final int[] mVisiblePageRange = new int[2];
private Launcher mLauncher;
private Workspace mWorkspace;
@@ -107,7 +108,7 @@
public void onAnimationEnd(Animator animation) {
mIsAnimating = false;
thresholdManager.reset();
- mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+ mWorkspace.onEndStateTransition();
}
});
animator.setDuration(duration).start();
@@ -162,9 +163,15 @@
} else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
// Passing threshold 3 ends the pinch and snaps to the new state.
if (startState == OVERVIEW && goingTowards == NORMAL) {
+ mLauncher.getUserEventDispatcher().logActionOnContainer(
+ LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE,
+ LauncherLogProto.OVERVIEW, mWorkspace.getCurrentPage());
mLauncher.showWorkspace(true);
mWorkspace.snapToPage(mWorkspace.getCurrentPage());
} else if (startState == NORMAL && goingTowards == OVERVIEW) {
+ mLauncher.getUserEventDispatcher().logActionOnContainer(
+ LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE,
+ LauncherLogProto.WORKSPACE, mWorkspace.getCurrentPage());
mLauncher.showOverviewMode(true);
}
} else {
@@ -173,17 +180,13 @@
}
private void setOverviewPanelsAlpha(float alpha, int duration) {
- mWorkspace.getVisiblePages(mVisiblePageRange);
- for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
- View page = mWorkspace.getPageAt(i);
- if (!mWorkspace.shouldDrawChild(page)) {
- continue;
- }
+ int childCount = mWorkspace.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
if (duration == 0) {
- ((CellLayout) page).setBackgroundAlpha(alpha);
+ cl.setBackgroundAlpha(alpha);
} else {
- ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
- .setDuration(duration).start();
+ ObjectAnimator.ofFloat(cl, "backgroundAlpha", alpha).setDuration(duration).start();
}
}
}
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 48a75d1..42515d1 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -61,12 +61,12 @@
mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
}
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
mPinchDetector.onTouchEvent(ev);
return mPinchStarted;
}
- public boolean onTouchEvent(MotionEvent ev) {
+ public boolean onControllerTouchEvent(MotionEvent ev) {
if (mPinchStarted) {
if (ev.getPointerCount() > 2) {
// Using more than two fingers causes weird behavior, so just cancel the pinch.
@@ -102,7 +102,7 @@
// once the state switching animation is complete.
return false;
}
- if (mLauncher.getTopFloatingView() != null) {
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
// Don't listen for the pinch gesture if a floating view is open.
return false;
}
@@ -112,7 +112,7 @@
mInterpolator = mWorkspace.isInOverviewMode() ? new LogDecelerateInterpolator(100, 0)
: new LogAccelerateInterpolator(100, 0);
mPinchStarted = true;
- mWorkspace.onLauncherTransitionPrepare(mLauncher, false, true);
+ mWorkspace.onPrepareStateTransition(true);
return true;
}
@@ -142,7 +142,7 @@
mThresholdManager);
} else {
mThresholdManager.reset();
- mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+ mWorkspace.onEndStateTransition();
}
mPinchStarted = false;
mPinchCanceled = false;
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index b064c47..efc0eac 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -18,7 +18,7 @@
private static final float ANIMATION_PROGRESS_STARTED = 0f;
private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
- private static final float MIN_SATUNATION = 0.2f;
+ private static final float MIN_SATURATION = 0.2f;
private static final float MIN_LIGHTNESS = 0.6f;
private static final float ICON_SCALE_FACTOR = 0.5f;
@@ -177,10 +177,8 @@
// Set the paint color only when the level changes, so that the dominant color
// is only calculated when needed.
mPaint.setColor(getIndicatorColor());
- }
- if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).setState(level <= 0 ?
- FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
+ } else if (mIcon instanceof FastBitmapDrawable) {
+ ((FastBitmapDrawable) mIcon).setIsDisabled(true);
}
invalidateSelf();
@@ -242,7 +240,7 @@
// Make sure that the dominant color has enough saturation to be visible properly.
float[] hsv = new float[3];
Color.colorToHSV(mIndicatorColor, hsv);
- if (hsv[1] < MIN_SATUNATION) {
+ if (hsv[1] < MIN_SATURATION) {
mIndicatorColor = DEFAULT_COLOR;
return mIndicatorColor;
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 37cbf98..5f89af6 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,13 +18,25 @@
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Paint;
import android.graphics.Rect;
+import android.support.annotation.IntDef;
import android.view.View;
import android.view.ViewGroup;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
public class ShortcutAndWidgetContainer extends ViewGroup {
- static final String TAG = "CellLayoutChildren";
+ static final String TAG = "ShortcutAndWidgetContainer";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DEFAULT, HOTSEAT, FOLDER})
+ public @interface ContainerType{}
+ public static final int DEFAULT = 0;
+ public static final int HOTSEAT = 1;
+ public static final int FOLDER = 2;
+
+ private int mContainerType = DEFAULT;
// 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.
@@ -32,14 +44,9 @@
private final WallpaperManager mWallpaperManager;
- private boolean mIsHotseatLayout;
-
private int mCellWidth;
private int mCellHeight;
- private int mWidthGap;
- private int mHeightGap;
-
private int mCountX;
private Launcher mLauncher;
@@ -52,12 +59,9 @@
mWallpaperManager = WallpaperManager.getInstance(context);
}
- public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap,
- int countX, int countY) {
+ public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) {
mCellWidth = cellWidth;
mCellHeight = cellHeight;
- mWidthGap = widthGap;
- mHeightGap = heightGap;
mCountX = countX;
}
@@ -92,8 +96,7 @@
}
public void setupLp(CellLayout.LayoutParams lp) {
- lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
- mCountX);
+ lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
}
// Set whether or not to invert the layout horizontally if the layout is in RTL mode.
@@ -101,20 +104,19 @@
mInvertIfRtl = invert;
}
- public void setIsHotseat(boolean isHotseat) {
- mIsHotseatLayout = isHotseat;
- }
-
- int getCellContentWidth() {
- final DeviceProfile grid = mLauncher.getDeviceProfile();
- return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
- grid.hotseatCellWidthPx: grid.cellWidthPx);
+ public void setContainerType(@ContainerType int containerType) {
+ mContainerType = containerType;
}
int getCellContentHeight() {
final DeviceProfile grid = mLauncher.getDeviceProfile();
- return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
- grid.hotseatCellHeightPx : grid.cellHeightPx);
+ int cellContentHeight = grid.cellHeightPx;
+ if (mContainerType == HOTSEAT) {
+ cellContentHeight = grid.hotseatCellHeightPx;
+ } else if (mContainerType == FOLDER) {
+ cellContentHeight = grid.folderCellHeightPx;
+ }
+ return Math.min(getMeasuredHeight(), cellContentHeight);
}
public void measureChild(View child) {
@@ -123,8 +125,7 @@
final int cellHeight = mCellHeight;
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (!lp.isFullscreen) {
- lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
- mCountX);
+ lp.setup(cellWidth, cellHeight, invertLayoutHorizontally(), mCountX);
if (child instanceof LauncherAppWidgetHostView) {
// Widgets have their own padding, so skip
@@ -201,28 +202,4 @@
child.cancelLongPress();
}
}
-
- @Override
- protected void setChildrenDrawingCacheEnabled(boolean enabled) {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View view = getChildAt(i);
- view.setDrawingCacheEnabled(enabled);
- // Update the drawing caches
- if (!view.isHardwareAccelerated() && enabled) {
- view.buildDrawingCache(true);
- }
- }
- }
-
- protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
- super.setChildrenDrawnWithCacheEnabled(enabled);
- }
-
- @Override
- public void setLayerType(int layerType, Paint paint) {
- // When clip children is disabled do not use hardware layer,
- // as hardware layer forces clip children.
- super.setLayerType(getClipChildren() ? layerType : LAYER_TYPE_NONE, paint);
- }
}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fb93743..d6d03d3 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -18,7 +18,6 @@
import android.annotation.TargetApi;
import android.content.ComponentName;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -31,7 +30,10 @@
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.ContentWriter;
/**
* Represents a launchable icon on the workspaces and in folders.
@@ -76,12 +78,6 @@
public Intent intent;
/**
- * Indicates whether we're using the default fallback icon instead of something from the
- * app.
- */
- boolean usingFallbackIcon;
-
- /**
* Indicates whether we're using a low res icon
*/
boolean usingLowResIcon;
@@ -131,7 +127,7 @@
* Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
* sd-card is not available).
*/
- int isDisabled = DEFAULT;
+ public int isDisabled = DEFAULT;
/**
* A message to display when the user tries to start a disabled shortcut.
@@ -139,7 +135,7 @@
*/
CharSequence disabledMessage;
- int status;
+ public int status;
/**
* The installation progress [0-100] of the package that this shortcut represents.
@@ -147,16 +143,11 @@
private int mInstallProgress;
/**
- * TODO move this to {@link #status}
- */
- int flags = 0;
-
- /**
* If this shortcut is a placeholder, then intent will be a market intent for the package, and
* this will hold the original intent from the database. Otherwise, null.
* Refer {@link #FLAG_RESTORED_ICON}, {@link #FLAG_AUTOINTALL_ICON}
*/
- Intent promisedIntent;
+ public Intent promisedIntent;
public ShortcutInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
@@ -188,11 +179,9 @@
intent = new Intent(info.intent);
iconResource = info.iconResource;
mIcon = info.mIcon; // TODO: should make a copy here. maybe we don't need this ctor at all
- flags = info.flags;
status = info.status;
mInstallProgress = info.mInstallProgress;
isDisabled = info.isDisabled;
- usingFallbackIcon = info.usingFallbackIcon;
}
/** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */
@@ -200,7 +189,6 @@
super(info);
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
- flags = info.flags;
isDisabled = info.isDisabled;
}
@@ -211,7 +199,6 @@
.getBadgedLabelForUser(info.getLabel(), info.getUser());
intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- flags = AppInfo.initFlags(info);
}
/**
@@ -221,7 +208,6 @@
public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
user = shortcutInfo.getUserHandle();
itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
- flags = 0;
updateFromDeepShortcutInfo(shortcutInfo, context);
}
@@ -248,25 +234,19 @@
}
@Override
- void onAddToDatabase(Context context, ContentValues values) {
- super.onAddToDatabase(context, values);
+ void onAddToDatabase(ContentWriter writer) {
+ super.onAddToDatabase(writer);
+ writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
+ .put(LauncherSettings.BaseLauncherColumns.INTENT, getPromisedIntent())
+ .put(LauncherSettings.Favorites.RESTORED, status);
- String titleStr = title != null ? title.toString() : null;
- values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
-
- String uri = promisedIntent != null ? promisedIntent.toUri(0)
- : (intent != null ? intent.toUri(0) : null);
- values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
- values.put(LauncherSettings.Favorites.RESTORED, status);
-
- if (!usingFallbackIcon && !usingLowResIcon) {
- writeBitmap(values, mIcon);
+ if (!usingLowResIcon) {
+ writer.putIcon(mIcon, user);
}
if (iconResource != null) {
- values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
- iconResource.packageName);
- values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
- iconResource.resourceName);
+ writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
+ .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+ iconResource.resourceName);
}
}
@@ -316,20 +296,20 @@
// TODO: Use cache for this
LauncherAppState launcherAppState = LauncherAppState.getInstance();
- Drawable unbadgedDrawable = launcherAppState.getShortcutManager()
+ Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
.getShortcutIconDrawable(shortcutInfo,
launcherAppState.getInvariantDeviceProfile().fillResIconDpi);
IconCache cache = launcherAppState.getIconCache();
Bitmap unbadgedBitmap = unbadgedDrawable == null
? cache.getDefaultIcon(UserHandleCompat.myUserHandle())
- : Utilities.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
+ : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
setIcon(getBadgedIcon(unbadgedBitmap, shortcutInfo, cache, context));
}
protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
IconCache cache, Context context) {
- unbadgedBitmap = Utilities.addShadowToIcon(unbadgedBitmap);
+ unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap);
// Get the app info for the source activity.
AppInfo appInfo = new AppInfo();
appInfo.user = user;
@@ -338,9 +318,9 @@
cache.getTitleAndIcon(appInfo, shortcutInfo.getActivityInfo(context), false);
} catch (NullPointerException e) {
// This may happen when we fail to load the activity info. Worst case ignore badging.
- return Utilities.badgeIconForUser(unbadgedBitmap, user, context);
+ return LauncherIcons.badgeIconForUser(unbadgedBitmap, user, context);
}
- return Utilities.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
+ return LauncherIcons.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
}
/** Returns the ShortcutInfo id associated with the deep shortcut. */
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9153943..7ea9aca 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -4,6 +4,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -12,6 +13,8 @@
import android.util.Pair;
import android.widget.Toast;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
public class UninstallDropTarget extends ButtonDropTarget {
@@ -49,23 +52,34 @@
}
}
- Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
- return componentInfo != null && (componentInfo.second & AppInfo.DOWNLOADED_FLAG) != 0;
+ return getUninstallTarget(context, info) != null;
}
/**
- * @return the component name and flags if {@param info} is an AppInfo or an app shortcut.
+ * @return the component name that should be uninstalled or null.
*/
- private static Pair<ComponentName, Integer> getAppInfoFlags(Object item) {
+ private static ComponentName getUninstallTarget(Context context, Object item) {
+ Intent intent = null;
+ UserHandleCompat user = null;
if (item instanceof AppInfo) {
AppInfo info = (AppInfo) item;
- return Pair.create(info.componentName, info.flags);
+ intent = info.intent;
+ user = info.user;
} 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);
+ if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
+ // Do not use restore/target intent here as we cannot uninstall an app which is
+ // being installed/restored.
+ intent = info.intent;
+ user = info.user;
+ }
+ }
+ if (intent != null) {
+ LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
+ .resolveActivity(intent, user);
+ if (info != null
+ && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return info.getComponentName();
}
}
return null;
@@ -93,11 +107,10 @@
public static boolean startUninstallActivity(
final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
- Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
- ComponentName cn = componentInfo.first;
+ final ComponentName cn = getUninstallTarget(launcher, info);
final boolean isUninstallable;
- if ((componentInfo.second & AppInfo.DOWNLOADED_FLAG) == 0) {
+ if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
// We may give them the option of disabling apps this way.
Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
@@ -112,8 +125,7 @@
isUninstallable = true;
}
if (callback != null) {
- sendUninstallResult(
- launcher, isUninstallable, componentInfo.first, info.user, callback);
+ sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
}
return isUninstallable;
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b0e096a..416ca8e 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -31,22 +31,16 @@
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.PowerManager;
+import android.os.TransactionTooLargeException;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -56,23 +50,17 @@
import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.graphics.ShadowGenerator;
-import com.android.launcher3.util.IconNormalizer;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import java.util.Set;
@@ -90,29 +78,26 @@
private static final String TAG = "Launcher.Utilities";
- private static final Rect sOldBounds = new Rect();
- private static final Canvas sCanvas = new Canvas();
-
private static final Pattern sTrimPattern =
Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
- static {
- sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
- Paint.FILTER_BITMAP_FLAG));
- }
- static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
- static int sColorIndex = 0;
-
private static final int[] sLoc0 = new int[2];
private static final int[] sLoc1 = new int[2];
+ private static final float[] sPoint = new float[2];
+ private static final Matrix sMatrix = new Matrix();
+ private static final Matrix sInverseMatrix = new Matrix();
- public static boolean isNycMR1OrAbove() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+ public static boolean isAtLeastO() {
+ // TODO: Clean this up: b/32610406
+ return !"REL".equals(Build.VERSION.CODENAME)
+ && "O".compareTo(Build.VERSION.CODENAME) <= 0;
}
- public static boolean isNycOrAbove() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
- }
+ public static final boolean ATLEAST_NOUGAT_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+
+ public static final boolean ATLEAST_NOUGAT =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
public static final boolean ATLEAST_MARSHMALLOW =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
@@ -159,7 +144,7 @@
}
public static boolean getAllowRotationDefaultValue(Context context) {
- if (isNycOrAbove()) {
+ if (ATLEAST_NOUGAT) {
// If the device was scaled, used the original dimensions to determine if rotation
// is allowed of not.
Resources res = context.getResources();
@@ -170,198 +155,6 @@
return false;
}
- public 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.
- */
- public static Bitmap createIconBitmap(String packageName, String resourceName,
- Context context) {
- PackageManager packageManager = context.getPackageManager();
- // the resource
- try {
- Resources resources = packageManager.getResourcesForApplication(packageName);
- if (resources != null) {
- final int id = resources.getIdentifier(resourceName, null, null);
- return createIconBitmap(
- resources.getDrawableForDensity(id, LauncherAppState.getInstance()
- .getInvariantDeviceProfile().fillResIconDpi), context);
- }
- } catch (Exception e) {
- // Icon not found.
- }
- return null;
- }
-
- private static int getIconBitmapSize() {
- return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
- }
-
- /**
- * Returns a bitmap which is of the appropriate size to be displayed as an icon
- */
- public static Bitmap createIconBitmap(Bitmap icon, Context context) {
- final int iconBitmapSize = getIconBitmapSize();
- if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
- return icon;
- }
- return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
- }
-
- /**
- * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
- * The bitmap is also visually normalized with other icons.
- */
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public static Bitmap createBadgedIconBitmap(
- Drawable icon, UserHandleCompat user, Context context) {
- float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
- 1 : IconNormalizer.getInstance().getScale(icon, null);
- Bitmap bitmap = createIconBitmap(icon, context, scale);
- return badgeIconForUser(bitmap, user, context);
- }
-
- /**
- * Badges the provided icon with the user badge if required.
- */
- public static Bitmap badgeIconForUser(Bitmap icon, UserHandleCompat user, Context context) {
- if (Utilities.ATLEAST_LOLLIPOP && user != null
- && !UserHandleCompat.myUserHandle().equals(user)) {
- BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
- Drawable badged = context.getPackageManager().getUserBadgedIcon(
- drawable, user.getUser());
- if (badged instanceof BitmapDrawable) {
- return ((BitmapDrawable) badged).getBitmap();
- } else {
- return createIconBitmap(badged, context);
- }
- } else {
- return icon;
- }
- }
-
- /**
- * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
- * normalized with other icons and has enough spacing to add shadow.
- */
- public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
- RectF iconBounds = new RectF();
- float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
- 1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
- scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
- return createIconBitmap(icon, context, scale);
- }
-
- /**
- * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
- * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
- */
- public static Bitmap addShadowToIcon(Bitmap icon) {
- return ShadowGenerator.getInstance().recreateIcon(icon);
- }
-
- /**
- * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
- */
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
- int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
- synchronized (sCanvas) {
- sCanvas.setBitmap(srcTgt);
- sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
- new Rect(srcTgt.getWidth() - badgeSize,
- srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
- new Paint(Paint.FILTER_BITMAP_FLAG));
- sCanvas.setBitmap(null);
- }
- return srcTgt;
- }
-
- /**
- * Returns a bitmap suitable for the all apps view.
- */
- public static Bitmap createIconBitmap(Drawable icon, Context context) {
- return createIconBitmap(icon, context, 1.0f /* scale */);
- }
-
- /**
- * @param scale the scale to apply before drawing {@param icon} on the canvas
- */
- public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
- synchronized (sCanvas) {
- final int iconBitmapSize = getIconBitmapSize();
-
- int width = iconBitmapSize;
- int height = iconBitmapSize;
-
- if (icon instanceof PaintDrawable) {
- PaintDrawable painter = (PaintDrawable) icon;
- painter.setIntrinsicWidth(width);
- painter.setIntrinsicHeight(height);
- } else if (icon instanceof BitmapDrawable) {
- // Ensure the bitmap has a density.
- BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
- Bitmap bitmap = bitmapDrawable.getBitmap();
- if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
- bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
- }
- }
- int sourceWidth = icon.getIntrinsicWidth();
- int sourceHeight = icon.getIntrinsicHeight();
- if (sourceWidth > 0 && sourceHeight > 0) {
- // Scale the icon proportionally to the icon dimensions
- final float ratio = (float) sourceWidth / sourceHeight;
- if (sourceWidth > sourceHeight) {
- height = (int) (width / ratio);
- } else if (sourceHeight > sourceWidth) {
- width = (int) (height * ratio);
- }
- }
-
- // no intrinsic size --> use default size
- int textureWidth = iconBitmapSize;
- int textureHeight = iconBitmapSize;
-
- final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
- Bitmap.Config.ARGB_8888);
- final Canvas canvas = sCanvas;
- canvas.setBitmap(bitmap);
-
- final int left = (textureWidth-width) / 2;
- final int top = (textureHeight-height) / 2;
-
- @SuppressWarnings("all") // suppress dead code warning
- final boolean debug = false;
- if (debug) {
- // draw a big box for the icon for debugging
- canvas.drawColor(sColors[sColorIndex]);
- if (++sColorIndex >= sColors.length) sColorIndex = 0;
- Paint debugPaint = new Paint();
- debugPaint.setColor(0xffcccc00);
- canvas.drawRect(left, top, left+width, top+height, debugPaint);
- }
-
- sOldBounds.set(icon.getBounds());
- icon.setBounds(left, top, left+width, top+height);
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
- icon.draw(canvas);
- canvas.restore();
- icon.setBounds(sOldBounds);
- canvas.setBitmap(null);
-
- return bitmap;
- }
- }
-
/**
* Given a coordinate relative to the descendant, find the coordinate in a parent view's
* coordinates.
@@ -377,68 +170,52 @@
*/
public static float getDescendantCoordRelativeToAncestor(
View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
- float[] pt = {coord[0], coord[1]};
+ sPoint[0] = coord[0];
+ sPoint[1] = coord[1];
+
float scale = 1.0f;
View v = descendant;
while(v != ancestor && v != null) {
// For TextViews, scroll has a meaning which relates to the text position
// which is very strange... ignore the scroll.
if (v != descendant || includeRootScroll) {
- pt[0] -= v.getScrollX();
- pt[1] -= v.getScrollY();
+ sPoint[0] -= v.getScrollX();
+ sPoint[1] -= v.getScrollY();
}
- v.getMatrix().mapPoints(pt);
- pt[0] += v.getLeft();
- pt[1] += v.getTop();
+ v.getMatrix().mapPoints(sPoint);
+ sPoint[0] += v.getLeft();
+ sPoint[1] += v.getTop();
scale *= v.getScaleX();
v = (View) v.getParent();
}
- coord[0] = Math.round(pt[0]);
- coord[1] = Math.round(pt[1]);
+ coord[0] = Math.round(sPoint[0]);
+ coord[1] = Math.round(sPoint[1]);
return scale;
}
/**
* Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
*/
- public static float mapCoordInSelfToDescendent(View descendant, View root,
- int[] coord) {
- ArrayList<View> ancestorChain = new ArrayList<View>();
-
- float[] pt = {coord[0], coord[1]};
-
+ public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) {
+ sMatrix.reset();
View v = descendant;
while(v != root) {
- ancestorChain.add(v);
+ sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
+ sMatrix.postConcat(v.getMatrix());
+ sMatrix.postTranslate(v.getLeft(), v.getTop());
v = (View) v.getParent();
}
- ancestorChain.add(root);
+ sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
+ sMatrix.invert(sInverseMatrix);
- float scale = 1.0f;
- Matrix inverse = new Matrix();
- int count = ancestorChain.size();
- for (int i = count - 1; i >= 0; i--) {
- View ancestor = ancestorChain.get(i);
- View next = i > 0 ? ancestorChain.get(i-1) : null;
-
- pt[0] += ancestor.getScrollX();
- pt[1] += ancestor.getScrollY();
-
- if (next != null) {
- pt[0] -= next.getLeft();
- pt[1] -= next.getTop();
- next.getMatrix().invert(inverse);
- inverse.mapPoints(pt);
- scale *= next.getScaleX();
- }
- }
-
- coord[0] = (int) Math.round(pt[0]);
- coord[1] = (int) Math.round(pt[1]);
- return scale;
+ sPoint[0] = coord[0];
+ sPoint[1] = coord[1];
+ sInverseMatrix.mapPoints(sPoint);
+ coord[0] = Math.round(sPoint[0]);
+ coord[1] = Math.round(sPoint[1]);
}
/**
@@ -452,30 +229,6 @@
localY < (v.getHeight() + slop);
}
- /** Translates MotionEvents from src's coordinate system to dst's. */
- public static void translateEventCoordinates(View src, View dst, MotionEvent dstEvent) {
- toGlobalMotionEvent(src, dstEvent);
- toLocalMotionEvent(dst, dstEvent);
- }
-
- /**
- * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private static void toGlobalMotionEvent(View view, MotionEvent event) {
- view.getLocationOnScreen(sLoc0);
- event.offsetLocation(sLoc0[0], sLoc0[1]);
- }
-
- /**
- * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private static void toLocalMotionEvent(View view, MotionEvent event) {
- view.getLocationOnScreen(sLoc0);
- event.offsetLocation(-sLoc0[0], -sLoc0[1]);
- }
-
public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
v0.getLocationInWindow(sLoc0);
v1.getLocationInWindow(sLoc1);
@@ -832,8 +585,8 @@
return ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
}
- public static boolean isWallapaperAllowed(Context context) {
- if (isNycOrAbove()) {
+ public static boolean isWallpaperAllowed(Context context) {
+ if (ATLEAST_NOUGAT) {
try {
WallpaperManager wm = context.getSystemService(WallpaperManager.class);
return (Boolean) wm.getClass().getDeclaredMethod("isSetWallpaperAllowed")
@@ -880,28 +633,6 @@
return c == null || c.isEmpty();
}
- /**
- * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
- * This allows the badging to be done based on the action bitmap size rather than
- * the scaled bitmap size.
- */
- private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
- public FixedSizeBitmapDrawable(Bitmap bitmap) {
- super(null, bitmap);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return getBitmap().getWidth();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return getBitmap().getWidth();
- }
- }
-
public static int getColorAccent(Context context) {
TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
int colorAccent = ta.getColor(0, 0);
@@ -919,4 +650,9 @@
accessibilityManager.sendAccessibilityEvent(event);
}
}
+
+ public static boolean isBinderSizeError(Exception e) {
+ return e.getCause() instanceof TransactionTooLargeException
+ || e.getCause() instanceof DeadObjectException;
+ }
}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 45e65b5..354b8ec 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -242,7 +242,7 @@
CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
+ CacheDb.COLUMN_SIZE + " = ?",
new String[]{
- key.componentName.flattenToString(),
+ key.componentName.flattenToShortString(),
Long.toString(mUserManager.getSerialNumberForUser(key.user)),
key.size
});
@@ -301,7 +301,14 @@
Drawable drawable = null;
if (info.previewImage != 0) {
- drawable = mWidgetManager.loadPreview(info);
+ try {
+ drawable = mWidgetManager.loadPreview(info);
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "Error loading widget preview for: " + info.provider, e);
+ // During OutOfMemoryError, the previous heap stack is not affected. Catching
+ // an OOM error here should be safe & not affect other parts of launcher.
+ drawable = null;
+ }
if (drawable != null) {
drawable = mutateOnMainThread(drawable);
} else {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 950a407..f6b5072 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,12 +28,10 @@
import android.app.WallpaperManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -50,17 +48,20 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.Launcher.LauncherOverlay;
import com.android.launcher3.UninstallDropTarget.DropTargetSource;
-import com.android.launcher3.accessibility.AccessibileDragListenerAdapter;
+import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -74,6 +75,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -86,7 +88,6 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
/**
@@ -96,7 +97,7 @@
*/
public class Workspace extends PagedView
implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
- DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
+ DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
Insettable, DropTargetSource {
private static final String TAG = "Launcher.Workspace";
@@ -170,11 +171,10 @@
// 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 static final Rect sTempRect = new Rect();
+
private final int[] mTempXY = new int[2];
@Thunk float[] mDragViewVisualCenter = new float[2];
private float[] mTempCellLayoutCenterCoordinates = new float[2];
- private int[] mTempVisiblePagesRange = new int[2];
- private Matrix mTempMatrix = new Matrix();
private SpringLoadedDragController mSpringLoadedDragController;
private float mOverviewModeShrinkFactor;
@@ -303,7 +303,7 @@
LauncherOverlay mLauncherOverlay;
boolean mScrollInteractionBegan;
boolean mStartedSendingScrollEvents;
- float mLastOverlaySroll = 0;
+ float mLastOverlayScroll = 0;
// Total over scrollX in the overlay direction.
private int mUnboundedScrollX;
private boolean mForceDrawAdjacentPages = false;
@@ -407,7 +407,12 @@
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragStart", 0, 0);
+ enforceDragParity("onDragStart", 0, 0);
+ }
+
+ if (mDragInfo != null && mDragInfo.cell != null) {
+ CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
+ layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
}
if (mOutlineProvider != null) {
@@ -464,7 +469,7 @@
@Override
public void onDragEnd() {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragEnd", 0, 0);
+ enforceDragParity("onDragEnd", 0, 0);
}
if (!mDeferRemoveExtraEmptyScreen) {
@@ -477,6 +482,8 @@
// Re-enable any Un/InstallShortcutReceiver and now process any queued items
InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
+ mOutlineProvider = null;
+ mDragInfo = null;
mDragSourceInternal = null;
mLauncher.onInteractionEnd();
}
@@ -492,7 +499,6 @@
setWillNotDraw(false);
setClipChildren(false);
setClipToPadding(false);
- setChildrenDrawnWithCacheEnabled(true);
setMinScale(mOverviewModeShrinkFactor);
setupLayoutTransition();
@@ -545,32 +551,6 @@
super.onChildViewAdded(parent, child);
}
- protected boolean shouldDrawChild(View child) {
- final CellLayout cl = (CellLayout) child;
- return super.shouldDrawChild(child) &&
- (mIsSwitchingState ||
- cl.getShortcutsAndWidgets().getAlpha() > 0 ||
- cl.getBackgroundAlpha() > 0);
- }
-
- /**
- * @return The open folder on the current screen, or null if there is none
- */
- public Folder getOpenFolder() {
- DragLayer dragLayer = mLauncher.getDragLayer();
- // Iterate in reverse order. Folder is added later to the dragLayer,
- // and will be one of the last views.
- for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
- View child = dragLayer.getChildAt(i);
- if (child instanceof Folder) {
- Folder folder = (Folder) child;
- if (folder.getInfo().opened)
- return folder;
- }
- }
- return null;
- }
-
boolean isTouchActive() {
return mTouchState != TOUCH_STATE_REST;
}
@@ -582,7 +562,7 @@
/**
* Initializes and binds the first page
- * @param qsb an exisitng qsb to recycle or null.
+ * @param qsb an existing qsb to recycle or null.
*/
public void bindAndInitFirstWorkspaceScreen(View qsb) {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
@@ -737,11 +717,7 @@
addFullScreenPage(customScreen);
// Update the custom content hint
- if (mRestorePage != INVALID_RESTORE_PAGE) {
- mRestorePage = mRestorePage + 1;
- } else {
- setCurrentPage(getCurrentPage() + 1);
- }
+ setCurrentPage(getCurrentPage() + 1);
}
public void removeCustomContentPage() {
@@ -762,11 +738,7 @@
mCustomContentCallbacks = null;
// Update the custom content hint
- if (mRestorePage != INVALID_RESTORE_PAGE) {
- mRestorePage = mRestorePage - 1;
- } else {
- setCurrentPage(getCurrentPage() - 1);
- }
+ setCurrentPage(getCurrentPage() - 1);
}
public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
@@ -1012,7 +984,7 @@
return;
}
- if (isPageMoving()) {
+ if (isPageInTransition()) {
mStripScreensOnPageStopMoving = true;
return;
}
@@ -1328,31 +1300,14 @@
}
}
- protected void onPageBeginMoving() {
- super.onPageBeginMoving();
-
- if (isHardwareAccelerated()) {
- updateChildrenLayersEnabled(false);
- } else {
- if (mNextPage != INVALID_PAGE) {
- // we're snapping to a particular screen
- enableChildrenCache(mCurrentPage, mNextPage);
- } else {
- // this is when user is actively dragging a particular screen, they might
- // swipe it either left or right (but we won't advance by more than one screen)
- enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
- }
- }
+ protected void onPageBeginTransition() {
+ super.onPageBeginTransition();
+ updateChildrenLayersEnabled(false);
}
- protected void onPageEndMoving() {
- super.onPageEndMoving();
-
- if (isHardwareAccelerated()) {
- updateChildrenLayersEnabled(false);
- } else {
- clearChildrenCache();
- }
+ protected void onPageEndTransition() {
+ super.onPageEndTransition();
+ updateChildrenLayersEnabled(false);
if (mDragController.isDragging()) {
if (workspaceInModalState()) {
@@ -1454,6 +1409,10 @@
if (!isTransitioning) {
showPageIndicatorAtCurrentScroll();
}
+
+ updatePageAlphaValues();
+ updateStateForCustomContent();
+ enableHwLayersOnVisiblePages();
}
private void showPageIndicatorAtCurrentScroll() {
@@ -1470,7 +1429,7 @@
boolean shouldScrollOverlay = mLauncherOverlay != null &&
((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
- boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlaySroll != 0 &&
+ boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 &&
((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl));
if (shouldScrollOverlay) {
@@ -1479,8 +1438,8 @@
mLauncherOverlay.onScrollInteractionBegin();
}
- mLastOverlaySroll = Math.abs(amount / getViewportWidth());
- mLauncherOverlay.onScrollChange(mLastOverlaySroll, mIsRtl);
+ mLastOverlayScroll = Math.abs(amount / getViewportWidth());
+ mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
} else if (shouldOverScroll) {
dampedOverScroll(amount);
}
@@ -1612,19 +1571,7 @@
}
@Override
- protected Matrix getPageShiftMatrix() {
- if (Float.compare(mOverlayTranslation, 0) != 0) {
- // The pages are translated by mOverlayTranslation. incorporate that in the
- // visible page calculation by shifting everything back by that same amount.
- mTempMatrix.set(getMatrix());
- mTempMatrix.postTranslate(-mOverlayTranslation, 0);
- return mTempMatrix;
- }
- return super.getPageShiftMatrix();
- }
-
- @Override
- protected void getEdgeVerticalPostion(int[] pos) {
+ protected void getEdgeVerticalPosition(int[] pos) {
View child = getChildAt(getPageCount() - 1);
pos[0] = child.getTop();
pos[1] = child.getBottom();
@@ -1723,15 +1670,16 @@
}
public void showOutlinesTemporarily() {
- if (!mIsPageMoving && !isTouchActive()) {
+ if (!mIsPageInTransition && !isTouchActive()) {
snapToPage(mCurrentPage);
}
}
- private void updatePageAlphaValues(int screenCenter) {
+ private void updatePageAlphaValues() {
if (mWorkspaceFadeInAdjacentScreens &&
!workspaceInModalState() &&
!mIsSwitchingState) {
+ int screenCenter = getScrollX() + getViewportWidth() / 2;
for (int i = numCustomPages(); i < getChildCount(); i++) {
CellLayout child = (CellLayout) getChildAt(i);
if (child != null) {
@@ -1756,10 +1704,10 @@
}
public boolean isOnOrMovingToCustomContent() {
- return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
+ return hasCustomContent() && getNextPage() == 0;
}
- private void updateStateForCustomContent(int screenCenter) {
+ private void updateStateForCustomContent() {
float translationX = 0;
float progress = 0;
if (hasCustomContent()) {
@@ -1807,13 +1755,6 @@
}
}
- @Override
- protected void screenScrolled(int screenCenter) {
- updatePageAlphaValues(screenCenter);
- updateStateForCustomContent(screenCenter);
- enableHwLayersOnVisiblePages();
- }
-
protected void onAttachedToWindow() {
super.onAttachedToWindow();
IBinder windowToken = getWindowToken();
@@ -1875,7 +1816,7 @@
}
});
}
-
+ updatePageAlphaValues();
}
@Override
@@ -1890,40 +1831,9 @@
return mState != State.NORMAL;
}
- void enableChildrenCache(int fromPage, int toPage) {
- if (fromPage > toPage) {
- final int temp = fromPage;
- fromPage = toPage;
- toPage = temp;
- }
-
- final int screenCount = getChildCount();
-
- fromPage = Math.max(fromPage, 0);
- toPage = Math.min(toPage, screenCount - 1);
-
- for (int i = fromPage; i <= toPage; i++) {
- final CellLayout layout = (CellLayout) getChildAt(i);
- layout.setChildrenDrawnWithCacheEnabled(true);
- layout.setChildrenDrawingCacheEnabled(true);
- }
- }
-
- void clearChildrenCache() {
- final int screenCount = getChildCount();
- for (int i = 0; i < screenCount; i++) {
- final CellLayout layout = (CellLayout) getChildAt(i);
- layout.setChildrenDrawnWithCacheEnabled(false);
- // In software mode, we don't want the items to continue to be drawn into bitmaps
- if (!isHardwareAccelerated()) {
- layout.setChildrenDrawingCacheEnabled(false);
- }
- }
- }
-
@Thunk void updateChildrenLayersEnabled(boolean force) {
boolean small = mState == State.OVERVIEW || mIsSwitchingState;
- boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
+ boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageInTransition();
if (enableChildrenLayers != mChildrenLayersEnabled) {
mChildrenLayersEnabled = enableChildrenLayers;
@@ -1941,9 +1851,37 @@
private void enableHwLayersOnVisiblePages() {
if (mChildrenLayersEnabled) {
final int screenCount = getChildCount();
- getVisiblePages(mTempVisiblePagesRange);
- int leftScreen = mTempVisiblePagesRange[0];
- int rightScreen = mTempVisiblePagesRange[1];
+
+ float visibleLeft = getViewportOffsetX();
+ float visibleRight = visibleLeft + getViewportWidth();
+ float scaleX = getScaleX();
+ if (scaleX < 1 && scaleX > 0) {
+ float mid = getMeasuredWidth() / 2;
+ visibleLeft = mid - ((mid - visibleLeft) / scaleX);
+ visibleRight = mid + ((visibleRight - mid) / scaleX);
+ }
+
+ int leftScreen = -1;
+ int rightScreen = -1;
+ for (int i = numCustomPages(); i < screenCount; i++) {
+ final View child = getPageAt(i);
+
+ float left = child.getLeft() + child.getTranslationX() - getScrollX();
+ if (left <= visibleRight && (left + child.getMeasuredWidth()) >= visibleLeft) {
+ if (leftScreen == -1) {
+ leftScreen = i;
+ }
+ rightScreen = i;
+ }
+ }
+ if (mForceDrawAdjacentPages) {
+ // In overview mode, make sure that the two side pages are visible.
+ leftScreen = Utilities.boundToRange(getCurrentPage() - 1,
+ numCustomPages(), rightScreen);
+ rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
+ leftScreen, getPageCount() - 1);
+ }
+
if (leftScreen == rightScreen) {
// make sure we're caching at least two pages always
if (rightScreen < screenCount - 1) {
@@ -1953,14 +1891,10 @@
}
}
- final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
- for (int i = 0; i < screenCount; i++) {
+ for (int i = numCustomPages(); i < screenCount; i++) {
final CellLayout layout = (CellLayout) getPageAt(i);
-
- // enable layers between left and right screen inclusive, except for the
- // customScreen, which may animate its content during transitions.
- boolean enableLayer = layout != customScreen &&
- leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
+ // enable layers between left and right screen inclusive.
+ boolean enableLayer = leftScreen <= i && i <= rightScreen;
layout.enableHardwareLayer(enableLayer);
}
}
@@ -1979,16 +1913,6 @@
updateChildrenLayersEnabled(false);
}
- @Override
- protected void getVisiblePages(int[] range) {
- super.getVisiblePages(range);
- if (mForceDrawAdjacentPages) {
- // In overview mode, make sure that the two side pages are visible.
- range[0] = Utilities.boundToRange(getCurrentPage() - 1, numCustomPages(), range[1]);
- range[1] = Utilities.boundToRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
- }
- }
-
protected void onWallpaperTap(MotionEvent ev) {
final int[] position = mTempXY;
getLocationOnScreen(position);
@@ -2009,7 +1933,7 @@
public void exitWidgetResizeMode() {
DragLayer dragLayer = mLauncher.getDragLayer();
- dragLayer.clearAllResizeFrames();
+ dragLayer.clearResizeFrame();
}
@Override
@@ -2106,7 +2030,7 @@
* to that new state.
*/
public Animator setStateWithAnimation(State toState, boolean animated,
- HashMap<View, Integer> layerViews) {
+ AnimationLayerSet layerViews) {
// Create the animation to the new state
AnimatorSet workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState,
toState, animated, layerViews);
@@ -2125,6 +2049,20 @@
mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null);
}
+ onPrepareStateTransition(mState.hasMultipleVisiblePages);
+
+ StateTransitionListener listener = new StateTransitionListener();
+ if (animated) {
+ ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
+ stepAnimator.addListener(listener);
+
+ workspaceAnim.play(stepAnimator);
+ workspaceAnim.addListener(listener);
+ } else {
+ listener.onAnimationStart(null);
+ listener.onAnimationEnd(null);
+ }
+
return workspaceAnim;
}
@@ -2177,9 +2115,7 @@
}
}
- @Override
- public void onLauncherTransitionPrepare(Launcher l, boolean animated,
- boolean multiplePagesVisible) {
+ public void onPrepareStateTransition(boolean multiplePagesVisible) {
mIsSwitchingState = true;
mTransitionProgress = 0;
@@ -2192,32 +2128,12 @@
hideCustomContentIfNecessary();
}
- @Override
- public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
- if (mPageIndicator != null) {
- boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED;
- mPageIndicator.setShouldAutoHide(!isNewStateSpringLoaded);
- if (isNewStateSpringLoaded) {
- // Show the page indicator at the same time as the rest of the transition.
- showPageIndicatorAtCurrentScroll();
- }
- }
- }
-
- @Override
- public void onLauncherTransitionStep(Launcher l, float t) {
- mTransitionProgress = t;
- }
-
- @Override
- public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ public void onEndStateTransition() {
mIsSwitchingState = false;
updateChildrenLayersEnabled(false);
showCustomContentIfNecessary();
mForceDrawAdjacentPages = false;
- if (mState == State.SPRING_LOADED) {
- showPageIndicatorAtCurrentScroll();
- }
+ mTransitionProgress = 1;
}
void updateCustomContentVisibility() {
@@ -2270,11 +2186,9 @@
mDragInfo = cellInfo;
child.setVisibility(INVISIBLE);
- CellLayout layout = (CellLayout) child.getParent().getParent();
- layout.prepareChildForDrag(child);
if (options.isAccessibleDrag) {
- mDragController.addDragListener(new AccessibileDragListenerAdapter(
+ mDragController.addDragListener(new AccessibleDragListenerAdapter(
this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
@Override
protected void enableAccessibleDrag(boolean enable) {
@@ -2348,6 +2262,15 @@
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
}
+ if (child instanceof BubbleTextView) {
+ DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon((BubbleTextView) child);
+ if (dsc != null) {
+ dragOptions.preDragCondition = dsc.createPreDragCondition();
+
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+ }
+
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
@@ -2423,18 +2346,7 @@
// Don't accept the drop if there's no room for the item
if (!foundCell) {
- // Don't show the message if we are dropping on the AllApps button and the hotseat
- // is full
- boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
- if (mTargetCell != null && isHotseat && !FeatureFlags.NO_ALL_APPS_ICON) {
- Hotseat hotseat = mLauncher.getHotseat();
- if (mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
- hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
- return false;
- }
- }
-
- mLauncher.showOutOfSpaceMessage(isHotseat);
+ onNoCellFound(dropTargetLayout);
return false;
}
}
@@ -2609,6 +2521,7 @@
onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
} else if (mDragInfo != null) {
final View cell = mDragInfo.cell;
+ boolean droppedOnOriginalCellDuringTransition = false;
if (dropTargetLayout != null && !d.cancelled) {
// Move internally
@@ -2651,6 +2564,10 @@
minSpanY = item.minSpanY;
}
+ droppedOnOriginalCellDuringTransition = mIsSwitchingState
+ && item.screenId == screenId && item.container == container
+ && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
+
int[] resultSpan = new int[2];
mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
@@ -2669,12 +2586,12 @@
resultSpan[1]);
}
- if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
- snapScreen = getPageIndexForScreenId(screenId);
- snapToPage(snapScreen);
- }
-
if (foundCell) {
+ if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
+ snapScreen = getPageIndexForScreenId(screenId);
+ snapToPage(snapScreen);
+ }
+
final ItemInfo info = (ItemInfo) cell.getTag();
if (hasMovedLayouts) {
// Reparent the view
@@ -2708,9 +2625,9 @@
&& !d.accessibleDrag) {
mDelayedResizeRunnable = new Runnable() {
public void run() {
- if (!isPageMoving() && !mIsSwitchingState) {
+ if (!isPageInTransition()) {
DragLayer dragLayer = mLauncher.getDragLayer();
- dragLayer.addResizeFrame(info, hostView, cellLayout);
+ dragLayer.addResizeFrame(hostView, cellLayout);
}
}
};
@@ -2720,6 +2637,8 @@
LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
lp.cellY, item.spanX, item.spanY);
} else {
+ onNoCellFound(dropTargetLayout);
+
// If we can't find a drop location, we return the item to its original position
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
mTargetCell[0] = lp.cellX;
@@ -2741,6 +2660,17 @@
};
mAnimatingViewIntoPlace = true;
if (d.dragView.hasDrawn()) {
+ if (droppedOnOriginalCellDuringTransition) {
+ // Animate the item to its original position, while simultaneously exiting
+ // spring-loaded mode so the page meets the icon where it was picked up.
+ mLauncher.getDragController().animateDragViewToOriginalPosition(
+ mDelayedResizeRunnable, cell,
+ mStateTransitionAnimation.mSpringLoadedTransitionTime);
+ mLauncher.exitSpringLoadedDragMode();
+ mLauncher.getDropTargetBar().onDragEnd();
+ parent.onDropChild(cell);
+ return;
+ }
final ItemInfo info = (ItemInfo) cell.getTag();
boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
@@ -2765,6 +2695,26 @@
}
}
+ public void onNoCellFound(View dropTargetLayout) {
+ if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+ Hotseat hotseat = mLauncher.getHotseat();
+ boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
+ && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
+ hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
+ if (!droppedOnAllAppsIcon) {
+ // Only show message when hotseat is full and drop target was not AllApps button
+ showOutOfSpaceMessage(true);
+ }
+ } else {
+ showOutOfSpaceMessage(false);
+ }
+ }
+
+ private void showOutOfSpaceMessage(boolean isHotseatLayout) {
+ int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+ Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
+ }
+
/**
* Computes the area relative to dragLayer which is used to display a page.
*/
@@ -2790,7 +2740,7 @@
@Override
public void onDragEnter(DragObject d) {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragEnter", 1, 1);
+ enforceDragParity("onDragEnter", 1, 1);
}
mCreateUserFolderOnDrop = false;
@@ -2807,13 +2757,13 @@
@Override
public void onDragExit(DragObject d) {
if (ENFORCE_DRAG_EVENT_ORDER) {
- enfoceDragParity("onDragExit", -1, 0);
+ enforceDragParity("onDragExit", -1, 0);
}
// Here we store the final page that will be dropped to, if the workspace in fact
// receives the drop
if (mInScrollArea) {
- if (isPageMoving()) {
+ if (isPageInTransition()) {
// If the user drops while the page is scrolling, we should use that page as the
// destination instead of the page that is being hovered over.
mDropToLayout = (CellLayout) getPageAt(getNextPage());
@@ -2840,14 +2790,14 @@
mLauncher.getDragLayer().hidePageHints();
}
- private void enfoceDragParity(String event, int update, int expectedValue) {
- enfoceDragParity(this, event, update, expectedValue);
+ private void enforceDragParity(String event, int update, int expectedValue) {
+ enforceDragParity(this, event, update, expectedValue);
for (int i = 0; i < getChildCount(); i++) {
- enfoceDragParity(getChildAt(i), event, update, expectedValue);
+ enforceDragParity(getChildAt(i), event, update, expectedValue);
}
}
- private void enfoceDragParity(View v, String event, int update, int expectedValue) {
+ private void enforceDragParity(View v, String event, int update, int expectedValue) {
Object tag = v.getTag(R.id.drag_event_parity);
int value = tag == null ? 0 : (Integer) tag;
value += update;
@@ -2968,7 +2918,7 @@
mTempXY[0] = (int) xy[0];
mTempXY[1] = (int) xy[1];
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
- mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY);
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY);
xy[0] = mTempXY[0];
xy[1] = mTempXY[1];
@@ -3656,8 +3606,10 @@
public void onDropCompleted(final View target, final DragObject d,
final boolean isFlingToDelete, final boolean success) {
if (mDeferDropAfterUninstall) {
+ final CellLayout.CellInfo dragInfo = mDragInfo;
mDeferredAction = new Runnable() {
public void run() {
+ mDragInfo = dragInfo; // Restore the drag info that was cleared in onDragEnd()
onDropCompleted(target, d, isFlingToDelete, success);
mDeferredAction = null;
}
@@ -3685,7 +3637,6 @@
&& mDragInfo.cell != null) {
mDragInfo.cell.setVisibility(VISIBLE);
}
- mOutlineProvider = null;
mDragInfo = null;
if (!isFlingToDelete) {
@@ -3813,7 +3764,7 @@
if (!workspaceInModalState() && !mIsSwitchingState) {
super.scrollLeft();
}
- Folder openFolder = getOpenFolder();
+ Folder openFolder = Folder.getOpen(mLauncher);
if (openFolder != null) {
openFolder.completeDragExit();
}
@@ -3824,7 +3775,7 @@
if (!workspaceInModalState() && !mIsSwitchingState) {
super.scrollRight();
}
- Folder openFolder = getOpenFolder();
+ Folder openFolder = Folder.getOpen(mLauncher);
if (openFolder != null) {
openFolder.completeDragExit();
}
@@ -3843,7 +3794,7 @@
}
boolean result = false;
- if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
+ if (!workspaceInModalState() && !mIsSwitchingState && Folder.getOpen(mLauncher) == null) {
mInScrollArea = true;
final int page = getNextPage() +
@@ -4003,63 +3954,34 @@
for (final CellLayout layoutParent: cellLayouts) {
final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
- final HashMap<ItemInfo, View> children = new HashMap<>();
+ LongArrayMap<View> idToViewMap = new LongArrayMap<>();
+ ArrayList<ItemInfo> items = new ArrayList<>();
for (int j = 0; j < layout.getChildCount(); j++) {
final View view = layout.getChildAt(j);
- children.put((ItemInfo) view.getTag(), view);
+ if (view.getTag() instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) view.getTag();
+ items.add(item);
+ idToViewMap.put(item.id, view);
+ }
}
- final ArrayList<View> childrenToRemove = new ArrayList<>();
- final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>();
- LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
- @Override
- public boolean filterItem(ItemInfo parent, ItemInfo info,
- ComponentName cn) {
- if (parent instanceof FolderInfo) {
- if (matcher.matches(info, cn)) {
- FolderInfo folder = (FolderInfo) parent;
- ArrayList<ShortcutInfo> appsToRemove;
- if (folderAppsToRemove.containsKey(folder)) {
- appsToRemove = folderAppsToRemove.get(folder);
- } else {
- appsToRemove = new ArrayList<ShortcutInfo>();
- folderAppsToRemove.put(folder, appsToRemove);
- }
- appsToRemove.add((ShortcutInfo) info);
- return true;
- }
- } else {
- if (matcher.matches(info, cn)) {
- childrenToRemove.add(children.get(info));
- return true;
- }
+ for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
+ View child = idToViewMap.get(itemToRemove.id);
+
+ if (child != null) {
+ // Note: We can not remove the view directly from CellLayoutChildren as this
+ // does not re-mark the spaces as unoccupied.
+ layoutParent.removeViewInLayout(child);
+ if (child instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) child);
}
- return false;
+ } else if (itemToRemove.container >= 0) {
+ // The item may belong to a folder.
+ View parent = idToViewMap.get(itemToRemove.container);
+ if (parent != null) {
+ ((FolderInfo) parent.getTag()).remove((ShortcutInfo) itemToRemove, false);
+ }
}
- };
- LauncherModel.filterItemInfos(children.keySet(), filter);
-
- // Remove all the apps from their folders
- for (FolderInfo folder : folderAppsToRemove.keySet()) {
- ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
- for (ShortcutInfo info : appsToRemove) {
- folder.remove(info, false);
- }
- }
-
- // Remove all the other children
- for (View child : childrenToRemove) {
- // Note: We can not remove the view directly from CellLayoutChildren as this
- // does not re-mark the spaces as unoccupied.
- layoutParent.removeViewInLayout(child);
- if (child instanceof DropTarget) {
- mDragController.removeDropTarget((DropTarget) child);
- }
- }
-
- if (childrenToRemove.size() > 0) {
- layout.requestLayout();
- layout.invalidate();
}
}
@@ -4160,8 +4082,9 @@
public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
HashSet<String> packages = new HashSet<>(1);
packages.add(packageName);
- LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
- removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user));
+ ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
+ LauncherModel.deleteItemsFromDatabase(mLauncher, matcher);
+ removeItemsByMatcher(matcher);
}
public void updateRestoreItems(final HashSet<ItemInfo> updates) {
@@ -4286,7 +4209,7 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.gridX = info.cellX;
target.gridY = info.cellY;
target.pageIndex = getCurrentPage();
@@ -4338,7 +4261,6 @@
@Override
public boolean evaluate(ItemInfo info, View view) {
if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
- PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view;
mLauncher.removeItem(view, info, false /* deleteFromDb */);
mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
}
@@ -4362,4 +4284,26 @@
public static final boolean isQsbContainerPage(int pageNo) {
return pageNo == 0;
}
+
+ private class StateTransitionListener extends AnimatorListenerAdapter
+ implements AnimatorUpdateListener {
+ @Override
+ public void onAnimationUpdate(ValueAnimator anim) {
+ mTransitionProgress = anim.getAnimatedFraction();
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mState == State.SPRING_LOADED) {
+ // Show the page indicator at the same time as the rest of the transition.
+ showPageIndicatorAtCurrentScroll();
+ }
+ mTransitionProgress = 0;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onEndStateTransition();
+ }
+ }
}
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 598ba74..1f36468 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,12 +30,11 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
+import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.Thunk;
-import java.util.HashMap;
-
/**
* A convenience class to update a view's visibility state after an alpha animation.
*/
@@ -226,7 +225,7 @@
}
public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
- boolean animated, HashMap<View, Integer> layerViews) {
+ boolean animated, AnimationLayerSet layerViews) {
AccessibilityManager am = (AccessibilityManager)
mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
final boolean accessibilityEnabled = am.isEnabled();
@@ -262,8 +261,7 @@
* Starts a transition animation for the workspace.
*/
private void animateWorkspace(final TransitionStates states, final boolean animated,
- final int duration, final HashMap<View, Integer> layerViews,
- final boolean accessibilityEnabled) {
+ final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) {
// Cancel existing workspace animations and create a new animator set if requested
cancelAnimation();
if (animated) {
@@ -396,12 +394,10 @@
// For animation optimization, we may need to provide the Launcher transition
// with a set of views on which to force build and manage layers in certain scenarios.
- layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- layerViews.put(qsbContainer, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- layerViews.put(mLauncher.getHotseat(),
- LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
- layerViews.put(mWorkspace.getPageIndicator(),
- LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+ layerViews.addView(overviewPanel);
+ layerViews.addView(qsbContainer);
+ layerViews.addView(mLauncher.getHotseat());
+ layerViews.addView(mWorkspace.getPageIndicator());
if (states.workspaceToOverview) {
hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
@@ -426,6 +422,11 @@
}
@Override
+ public void onAnimationStart(Animator animation) {
+ mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mStateAnimator = null;
if (canceled) return;
@@ -438,6 +439,7 @@
} else {
overviewPanel.setAlpha(finalOverviewPanelAlpha);
AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
+ mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
qsbAlphaAnimation.end();
mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
diff --git a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
similarity index 93%
rename from src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java
rename to src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
index 62a9a6d..f8df5d7 100644
--- a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
@@ -28,7 +28,7 @@
* Utility listener to enable/disable accessibility drag flags for a ViewGroup
* containing CellLayouts
*/
-public class AccessibileDragListenerAdapter implements DragListener {
+public class AccessibleDragListenerAdapter implements DragListener {
private final ViewGroup mViewGroup;
private final int mDragType;
@@ -38,7 +38,7 @@
* @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or
* {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG}
*/
- public AccessibileDragListenerAdapter(ViewGroup parent, int dragType) {
+ public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) {
mViewGroup = parent;
mDragType = dragType;
}
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 173aad0..83391f3 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -57,7 +57,7 @@
protected static final int MOVE = R.id.action_move;
protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
protected static final int RESIZE = R.id.action_resize;
- protected static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
+ public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
public enum DragType {
ICON,
@@ -100,14 +100,17 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- addActions(host, info);
+ addSupportedActions(host, info, false);
}
- protected void addActions(View host, AccessibilityNodeInfo info) {
+ public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
if (!(host.getTag() instanceof ItemInfo)) return;
ItemInfo item = (ItemInfo) host.getTag();
- if (host instanceof BubbleTextView && ((BubbleTextView) host).hasDeepShortcuts()) {
+ // If the request came from keyboard, do not add custom shortcuts as that is already
+ // exposed as a direct shortcut
+ if (!fromKeyboard && host instanceof BubbleTextView
+ && ((BubbleTextView) host).hasDeepShortcuts()) {
info.addAction(mActions.get(DEEP_SHORTCUTS));
}
@@ -121,9 +124,10 @@
info.addAction(mActions.get(INFO));
}
- if ((item instanceof ShortcutInfo)
+ // Do not add move actions for keyboard request as this uses virtual nodes.
+ if (!fromKeyboard && ((item instanceof ShortcutInfo)
|| (item instanceof LauncherAppWidgetInfo)
- || (item instanceof FolderInfo)) {
+ || (item instanceof FolderInfo))) {
info.addAction(mActions.get(MOVE));
if (item.container >= 0) {
@@ -188,8 +192,8 @@
});
return true;
} else if (action == MOVE_TO_WORKSPACE) {
- Folder folder = mLauncher.getWorkspace().getOpenFolder();
- mLauncher.closeFolder(folder, true);
+ Folder folder = Folder.getOpen(mLauncher);
+ folder.close(true);
ShortcutInfo info = (ShortcutInfo) item;
folder.getInfo().remove(info, false);
@@ -369,12 +373,10 @@
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
- Workspace workspace = mLauncher.getWorkspace();
-
- Folder folder = workspace.getOpenFolder();
+ Folder folder = Folder.getOpen(mLauncher);
if (folder != null) {
if (!folder.getItemsInReadingOrder().contains(item)) {
- mLauncher.closeFolder();
+ folder.close(true);
folder = null;
}
}
@@ -386,7 +388,7 @@
if (folder != null) {
folder.startDrag(cellInfo.cell, options);
} else {
- workspace.startDrag(cellInfo, options);
+ mLauncher.getWorkspace().startDrag(cellInfo, options);
}
}
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
index 385a766..29dd95c 100644
--- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
@@ -44,7 +44,7 @@
Context context = host.getContext();
info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
- if (Utilities.isWallapaperAllowed(context)) {
+ if (Utilities.isWallpaperAllowed(context)) {
info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
}
info.addAction(new AccessibilityAction(WIDGETS, context.getText(WIDGETS)));
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 0baa8f3..f7ca703 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -19,6 +19,7 @@
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherModel;
@@ -40,8 +41,10 @@
}
@Override
- protected void addActions(View host, AccessibilityNodeInfo info) {
- info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+ if ((host.getParent() instanceof DeepShortcutView)) {
+ info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ }
}
@Override
@@ -62,7 +65,7 @@
ArrayList<ItemInfo> itemList = new ArrayList<>();
itemList.add(info);
mLauncher.bindItems(itemList, 0, itemList.size(), true);
- mLauncher.closeShortcutsContainer();
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
announceConfirmation(R.string.item_added_to_workspace);
}
};
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 77ef642..5fc1d97 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,11 +15,11 @@
*/
package com.android.launcher3.allapps;
-import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.InsetDrawable;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.Spannable;
@@ -31,25 +31,22 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherTransitionable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -58,112 +55,32 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.List;
-
-/**
- * A merge algorithm that merges every section indiscriminately.
- */
-final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
-
- @Override
- public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
- AlphabeticalAppsList.SectionInfo withSection,
- int sectionAppCount, int numAppsPerRow, int mergeCount) {
- // Don't merge the predicted apps
- if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
- return false;
- }
- // Otherwise, merge every other section
- return true;
- }
-}
-
-/**
- * The logic we use to merge multiple sections. We only merge sections when their final row
- * contains less than a certain number of icons, and stop at a specified max number of merges.
- * In addition, we will try and not merge sections that identify apps from different scripts.
- */
-final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
-
- private int mMinAppsPerRow;
- private int mMinRowsInMergedSection;
- private int mMaxAllowableMerges;
- private CharsetEncoder mAsciiEncoder;
-
- public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
- mMinAppsPerRow = minAppsPerRow;
- mMinRowsInMergedSection = minRowsInMergedSection;
- mMaxAllowableMerges = maxNumMerges;
- mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
- }
-
- @Override
- public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
- AlphabeticalAppsList.SectionInfo withSection,
- int sectionAppCount, int numAppsPerRow, int mergeCount) {
- // Don't merge the predicted apps
- if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
- return false;
- }
-
- // Continue merging if the number of hanging apps on the final row is less than some
- // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
- // and while the number of merged sections is less than some fixed number of merges
- int rows = sectionAppCount / numAppsPerRow;
- int cols = sectionAppCount % numAppsPerRow;
-
- // Ensure that we do not merge across scripts, currently we only allow for english and
- // native scripts so we can test if both can just be ascii encoded
- boolean isCrossScript = false;
- if (section.firstAppItem != null && withSection.firstAppItem != null) {
- isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
- mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
- }
- return (0 < cols && cols < mMinAppsPerRow) &&
- rows < mMinRowsInMergedSection &&
- mergeCount < mMaxAllowableMerges &&
- !isCrossScript;
- }
-}
-
/**
* The all apps view container.
*/
public class AllAppsContainerView extends BaseContainerView implements DragSource,
- LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
-
- private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
- private static final int MAX_NUM_MERGES_PHONE = 2;
+ View.OnLongClickListener, AllAppsSearchBarController.Callbacks, Insettable {
private final Launcher mLauncher;
private final AlphabeticalAppsList mApps;
private final AllAppsGridAdapter mAdapter;
private final RecyclerView.LayoutManager mLayoutManager;
- private final RecyclerView.ItemDecoration mItemDecoration;
-
- // The computed bounds of the container
- private final Rect mContentBounds = new Rect();
private AllAppsRecyclerView mAppsRecyclerView;
private AllAppsSearchBarController mSearchBarController;
private View mSearchContainer;
+ private int mSearchContainerMinHeight;
private ExtendedEditText mSearchInput;
private HeaderElevationController mElevationController;
- private int mSearchContainerOffsetTop;
private SpannableStringBuilder mSearchQueryBuilder = null;
- private int mSectionNamesMargin;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
- private int mRecyclerViewBottomPadding;
- // This coordinate is relative to this container view
- private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
public AllAppsContainerView(Context context) {
this(context, null);
@@ -175,27 +92,37 @@
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- Resources res = context.getResources();
mLauncher = Launcher.getLauncher(context);
- mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
mApps = new AlphabeticalAppsList(context);
mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
- mItemDecoration = mAdapter.getItemDecoration();
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
- mRecyclerViewBottomPadding = 0;
- setPadding(0, 0, 0, 0);
- } else {
- mRecyclerViewBottomPadding =
- res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding);
- }
mSearchQueryBuilder = new SpannableStringBuilder();
+ mSearchContainerMinHeight
+ = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
+
Selection.setSelection(mSearchQueryBuilder, 0);
}
+ @Override
+ protected void updateBackground(
+ int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+ getRevealView().setBackground(new InsetDrawable(mBaseDrawable,
+ paddingLeft, paddingTop, paddingRight, paddingBottom));
+ getContentView().setBackground(
+ new InsetDrawable(new ColorDrawable(Color.TRANSPARENT),
+ paddingLeft, paddingTop, paddingRight, paddingBottom));
+ } else {
+ getRevealView().setBackground(mBaseDrawable);
+ }
+ } else {
+ super.updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+ }
+
/**
* Sets the current set of predicted apps.
*/
@@ -268,7 +195,7 @@
int[] point = new int[2];
point[0] = (int) ev.getX();
point[1] = (int) ev.getY();
- Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
+ Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point);
// IF the MotionEvent is inside the search box, and the container keeps on receiving
// touch input, container should move down.
@@ -282,13 +209,13 @@
}
// IF a shortcuts container is open, container should not be pulled down.
- if (mLauncher.getOpenShortcutsContainer() != null) {
+ if (DeepShortcutsContainer.getOpen(mLauncher) != null) {
return false;
}
// IF scroller is at the very top OR there is no scroll bar because there is probably not
// enough items to scroll, THEN it's okay for the container to be pulled down.
- if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) {
+ if (mAppsRecyclerView.getScrollBar().getThumbOffsetY() <= 0) {
return true;
}
return false;
@@ -340,9 +267,6 @@
0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
mSearchInput.setHint(spanned);
- mSearchContainerOffsetTop = getResources().getDimensionPixelSize(
- R.dimen.all_apps_search_bar_margin_top);
-
mElevationController = Utilities.ATLEAST_LOLLIPOP
? new HeaderElevationController.ControllerVL(mSearchContainer)
: new HeaderElevationController.ControllerV16(mSearchContainer);
@@ -356,10 +280,6 @@
mAppsRecyclerView.addOnScrollListener(mElevationController);
mAppsRecyclerView.setElevationController(mElevationController);
- if (mItemDecoration != null) {
- mAppsRecyclerView.addItemDecoration(mItemDecoration);
- }
-
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
mAppsRecyclerView.preMeasureViews(mAdapter);
@@ -373,15 +293,15 @@
}
@Override
+ public View getTouchDelegateTargetView() {
+ return mAppsRecyclerView;
+ }
+
+ @Override
public void onBoundsChanged(Rect newBounds) { }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthPx = MeasureSpec.getSize(widthMeasureSpec);
- int heightPx = MeasureSpec.getSize(heightMeasureSpec);
- updatePaddingsAndMargins(widthPx, heightPx);
- mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
-
DeviceProfile grid = mLauncher.getDeviceProfile();
grid.updateAppsViewNumCols();
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
@@ -392,18 +312,15 @@
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
- mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
- if (mNumAppsPerRow > 0) {
- int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
- final int thumbMaxWidth =
- getResources().getDimensionPixelSize(
- R.dimen.container_fastscroll_thumb_max_width);
- mSearchContainer.setPadding(
- rvPadding - mContainerPaddingLeft + thumbMaxWidth,
- mSearchContainer.getPaddingTop(),
- rvPadding - mContainerPaddingRight + thumbMaxWidth,
- mSearchContainer.getPaddingBottom());
- }
+ mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+ }
+ if (!grid.isVerticalBarLayout()) {
+ MarginLayoutParams searchContainerLp =
+ (MarginLayoutParams) mSearchContainer.getLayoutParams();
+
+ searchContainerLp.height = mLauncher.getDragLayer().getInsets().top
+ + mSearchContainerMinHeight;
+ mSearchContainer.setLayoutParams(searchContainerLp);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
@@ -412,98 +329,21 @@
// --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
// Update the number of items in the grid before we measure the view
- // TODO: mSectionNamesMargin is currently 0, but also account for it,
- // if it's enabled in the future.
grid.updateAppsViewNumCols();
if (mNumAppsPerRow != grid.allAppsNumCols ||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
mNumAppsPerRow = grid.allAppsNumCols;
mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
- // If there is a start margin to draw section names, determine how we are going to merge
- // app sections
- boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
- AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
- new FullMergeAlgorithm() :
- new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
- MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
-
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
- mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
+ mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
}
// --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- /**
- * Update the background and padding of the Apps view and children. Instead of insetting the
- * container view, we inset the background and padding of the recycler view to allow for the
- * recycler view to handle touch events (for fast scrolling) all the way to the edge.
- */
- private void updatePaddingsAndMargins(int widthPx, int heightPx) {
- Rect bgPadding = new Rect();
- getRevealView().getBackground().getPadding(bgPadding);
-
- mAppsRecyclerView.updateBackgroundPadding(bgPadding);
- mAdapter.updateBackgroundPadding(bgPadding);
- mElevationController.updateBackgroundPadding(bgPadding);
-
- // Pad the recycler view by the background padding plus the start margin (for the section
- // names)
- int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
- int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
- if (Utilities.isRtl(getResources())) {
- mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
- + startInset, mRecyclerViewBottomPadding);
- } else {
- mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
- maxScrollBarWidth, mRecyclerViewBottomPadding);
- }
-
- MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
- lp.leftMargin = bgPadding.left;
- lp.rightMargin = bgPadding.right;
-
- // Clip the view to the left and right edge of the background to
- // to prevent shadows from rendering beyond the edges
- final Rect newClipBounds = new Rect(
- bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
- setClipBounds(newClipBounds);
-
- // Allow the overscroll effect to reach the edges of the view
- mAppsRecyclerView.setClipToPadding(false);
-
- DeviceProfile grid = mLauncher.getDeviceProfile();
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
- if (!grid.isVerticalBarLayout()) {
- MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
-
- Rect insets = mLauncher.getDragLayer().getInsets();
- getContentView().setPadding(0, 0, 0, 0);
- int height = insets.top + grid.hotseatCellHeightPx;
-
- mlp.topMargin = height;
- mAppsRecyclerView.setLayoutParams(mlp);
-
- mSearchContainer.setPadding(
- mSearchContainer.getPaddingLeft(),
- insets.top + mSearchContainerOffsetTop,
- mSearchContainer.getPaddingRight(),
- mSearchContainer.getPaddingBottom());
- lp.height = height;
-
- View navBarBg = findViewById(R.id.nav_bar_bg);
- ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
- params.height = insets.bottom;
- navBarBg.setLayoutParams(params);
- navBarBg.setVisibility(View.VISIBLE);
- }
- }
- mSearchContainer.setLayoutParams(lp);
- }
-
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Determine if the key event was actual text, if so, focus the search bar and then dispatch
@@ -526,18 +366,7 @@
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return handleTouchEvent(ev);
- }
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return handleTouchEvent(ev);
- }
-
- @Override
- public boolean onLongClick(View v) {
+ public boolean onLongClick(final 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
@@ -549,22 +378,20 @@
if (mLauncher.getDragController().isDragging()) return false;
// Start the drag
- DragOptions dragOptions = new DragOptions();
- if (v instanceof BubbleTextView) {
- final BubbleTextView icon = (BubbleTextView) v;
- if (icon.hasDeepShortcuts()) {
- DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
- if (dsc != null) {
- dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() {
- @Override
- public void run() {
- icon.setVisibility(VISIBLE);
- }
- });
- }
+ final DragController dragController = mLauncher.getDragController();
+ dragController.addDragListener(new DragController.DragListener() {
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ v.setVisibility(INVISIBLE);
}
- }
- mLauncher.getWorkspace().beginDragShared(v, this, dragOptions);
+
+ @Override
+ public void onDragEnd() {
+ v.setVisibility(VISIBLE);
+ dragController.removeDragListener(this);
+ }
+ });
+ mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
// Enter spring loaded mode (the new workspace does this in
// onDragStart(), so we don't want to do it here)
@@ -615,101 +442,12 @@
}
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 && !mLauncher.getDragController().isDeferringDrag()) {
- int currentScreen = mLauncher.getCurrentWorkspaceScreen();
- Workspace workspace = (Workspace) target;
- CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
- ItemInfo itemInfo = d.dragInfo;
- if (layout != null) {
- showOutOfSpaceMessage =
- !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
- }
- }
- if (showOutOfSpaceMessage) {
- mLauncher.showOutOfSpaceMessage(false);
- }
-
d.deferDragViewCleanupPostAnimation = false;
}
}
@Override
- public void onLauncherTransitionPrepare(Launcher l, boolean animated,
- boolean multiplePagesVisible) {
- // 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 (toWorkspace) {
- reset();
- }
- }
-
- /**
- * Handles the touch events to dismiss all apps when clicking outside the bounds of the
- * recycler view.
- */
- private boolean handleTouchEvent(MotionEvent ev) {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (!mContentBounds.isEmpty()) {
- // Outset the fixed bounds and check if the touch is outside all apps
- Rect tmpRect = new Rect(mContentBounds);
- tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
- if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
- mBoundsCheckLastTouchDownPos.set(x, y);
- return true;
- }
- } else {
- // Check if the touch is outside all apps
- if (ev.getX() < getPaddingLeft() ||
- ev.getX() > (getWidth() - getPaddingRight())) {
- mBoundsCheckLastTouchDownPos.set(x, y);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mBoundsCheckLastTouchDownPos.x > -1) {
- ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
- float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
- float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
- float distance = (float) Math.hypot(dx, dy);
- if (distance < viewConfig.getScaledTouchSlop()) {
- // The background was clicked, so just go home
- Launcher launcher = Launcher.getLauncher(getContext());
- launcher.showWorkspace(true);
- return true;
- }
- }
- // Fall through
- case MotionEvent.ACTION_CANCEL:
- mBoundsCheckLastTouchDownPos.set(-1, -1);
- break;
- }
- return false;
- }
-
- @Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
if (mApps.setOrderedFilter(apps)) {
@@ -732,11 +470,29 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
targetParent.containerType = mAppsRecyclerView.getContainerType(v);
}
public boolean shouldRestoreImeState() {
return !TextUtils.isEmpty(mSearchInput.getText());
}
+
+ @Override
+ public void setInsets(Rect insets) {
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ if (grid.isVerticalBarLayout()) {
+ ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+ mlp.leftMargin = insets.left;
+ mlp.topMargin = insets.top;
+ mlp.rightMargin = insets.right;
+ setLayoutParams(mlp);
+ } else {
+ View navBarBg = findViewById(R.id.nav_bar_bg);
+ ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
+ navBarBgLp.height = insets.bottom;
+ navBarBg.setLayoutParams(navBarBgLp);
+ navBarBg.setVisibility(View.VISIBLE);
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 76934af..28b7685 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -29,12 +29,11 @@
private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
- private static final float FAST_SCROLL_TOUCH_VELOCITY_BARRIER = 1900f;
private AllAppsRecyclerView mRv;
private AlphabeticalAppsList mApps;
- // Keeps track of the current and targetted fast scroll section (the section to scroll to after
+ // Keeps track of the current and targeted fast scroll section (the section to scroll to after
// the initial delay)
int mTargetFastScrollPosition = -1;
@Thunk String mCurrentFastScrollSection;
@@ -187,9 +186,9 @@
public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
// Update newly bound views to the current fast scroll state if we are fast scrolling
if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
- if (holder.mContent instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+ if (holder.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
- (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.mContent;
+ (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.itemView;
updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
mTrackedFastScrollViews.add(v);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 7b6aef1..bd877f2 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,10 +18,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -41,10 +38,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-
-import java.util.HashMap;
-import java.util.List;
/**
* The grid view adapter of all the apps.
@@ -52,10 +45,7 @@
public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
public static final String TAG = "AppsGridAdapter";
- private static final boolean DEBUG = false;
- // A section break in the grid
- public static final int VIEW_TYPE_SECTION_BREAK = 1 << 0;
// A normal icon
public static final int VIEW_TYPE_ICON = 1 << 1;
// A prediction icon
@@ -78,25 +68,22 @@
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
| VIEW_TYPE_SEARCH_MARKET_DIVIDER
- | VIEW_TYPE_PREDICTION_DIVIDER
- | VIEW_TYPE_SECTION_BREAK;
+ | VIEW_TYPE_PREDICTION_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
| VIEW_TYPE_PREDICTION_ICON;
public interface BindViewCallback {
- public void onBindView(ViewHolder holder);
+ void onBindView(ViewHolder holder);
}
/**
* ViewHolder for each icon.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
- public View mContent;
public ViewHolder(View v) {
super(v);
- mContent = v;
}
}
@@ -158,189 +145,14 @@
}
}
- /**
- * Helper class to draw the section headers
- */
- public class GridItemDecoration extends RecyclerView.ItemDecoration {
-
- private static final boolean DEBUG_SECTION_MARGIN = false;
- private static final boolean FADE_OUT_SECTIONS = false;
-
- private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
- private Rect mTmpBounds = new Rect();
-
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- if (mApps.hasFilter() || mAppsPerRow == 0) {
- return;
- }
-
- if (DEBUG_SECTION_MARGIN) {
- Paint p = new Paint();
- p.setColor(0x33ff0000);
- c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mSectionNamesMargin,
- parent.getMeasuredHeight(), p);
- }
-
- List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
- boolean showSectionNames = mSectionNamesMargin > 0;
- int childCount = parent.getChildCount();
- int lastSectionTop = 0;
- int lastSectionHeight = 0;
- for (int i = 0; i < childCount; i++) {
- View child = parent.getChildAt(i);
- ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
- if (!isValidHolderAndChild(holder, child, items)) {
- continue;
- }
-
- if (showSectionNames && shouldDrawItemSection(holder, i, items)) {
- // At this point, we only draw sections for each section break;
- int viewTopOffset = (2 * child.getPaddingTop());
- int pos = holder.getPosition();
- AlphabeticalAppsList.AdapterItem item = items.get(pos);
- AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo;
-
- // Draw all the sections for this index
- String lastSectionName = item.sectionName;
- for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
- AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
- String sectionName = nextItem.sectionName;
- if (nextItem.sectionInfo != sectionInfo) {
- break;
- }
- if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
- continue;
- }
-
- // Find the section name bounds
- PointF sectionBounds = getAndCacheSectionBounds(sectionName);
-
- // Calculate where to draw the section
- int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
- int x = mIsRtl ?
- parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin :
- mBackgroundPadding.left;
- x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f);
- int y = child.getTop() + sectionBaseline;
-
- // Determine whether this is the last row with apps in that section, if
- // so, then fix the section to the row allowing it to scroll past the
- // baseline, otherwise, bound it to the baseline so it's in the viewport
- int appIndexInSection = items.get(pos).sectionAppIndex;
- int nextRowPos = Math.min(items.size() - 1,
- pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
- AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
- boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
- if (!fixedToRow) {
- y = Math.max(sectionBaseline, y);
- }
-
- // In addition, if it overlaps with the last section that was drawn, then
- // offset it so that it does not overlap
- if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) {
- y += lastSectionTop - y + lastSectionHeight;
- }
-
- // Draw the section header
- if (FADE_OUT_SECTIONS) {
- int alpha = 255;
- if (fixedToRow) {
- alpha = Math.min(255,
- (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
- }
- mSectionTextPaint.setAlpha(alpha);
- }
- c.drawText(sectionName, x, y, mSectionTextPaint);
-
- lastSectionTop = y;
- lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
- lastSectionName = sectionName;
- }
- i += (sectionInfo.numApps - item.sectionAppIndex);
- }
- }
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
- RecyclerView.State state) {
- // Do nothing
- }
-
- /**
- * Given a section name, return the bounds of the given section name.
- */
- private PointF getAndCacheSectionBounds(String sectionName) {
- PointF bounds = mCachedSectionBounds.get(sectionName);
- if (bounds == null) {
- mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
- bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
- mCachedSectionBounds.put(sectionName, bounds);
- }
- return bounds;
- }
-
- /**
- * Returns whether we consider this a valid view holder for us to draw a divider or section for.
- */
- private boolean isValidHolderAndChild(ViewHolder holder, View child,
- List<AlphabeticalAppsList.AdapterItem> items) {
- // Ensure item is not already removed
- GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
- child.getLayoutParams();
- if (lp.isItemRemoved()) {
- return false;
- }
- // Ensure we have a valid holder
- if (holder == null) {
- return false;
- }
- // Ensure we have a holder position
- int pos = holder.getPosition();
- if (pos < 0 || pos >= items.size()) {
- return false;
- }
- return true;
- }
-
- /**
- * Returns whether to draw the section for the given child.
- */
- private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
- List<AlphabeticalAppsList.AdapterItem> items) {
- int pos = holder.getPosition();
- AlphabeticalAppsList.AdapterItem item = items.get(pos);
-
- // Ensure it's an icon
- if (item.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
- return false;
- }
- // Draw the section header for the first item in each section
- return (childIndex == 0) ||
- (items.get(pos - 1).viewType == AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK);
- }
- }
-
private final Launcher mLauncher;
private final LayoutInflater mLayoutInflater;
private final AlphabeticalAppsList mApps;
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
- private final GridItemDecoration mItemDecoration;
private final View.OnClickListener mIconClickListener;
private final View.OnLongClickListener mIconLongClickListener;
- private final Rect mBackgroundPadding = new Rect();
- private final boolean mIsRtl;
-
- // Section drawing
- @Deprecated
- private final int mSectionNamesMargin;
- @Deprecated
- private final int mSectionHeaderOffset;
- private final Paint mSectionTextPaint;
-
private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
@@ -361,18 +173,9 @@
mGridSizer = new GridSpanSizer();
mGridLayoutMgr = new AppsGridLayoutManager(launcher);
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
- mItemDecoration = new GridItemDecoration();
mLayoutInflater = LayoutInflater.from(launcher);
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
- mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
- mIsRtl = Utilities.isRtl(res);
-
- mSectionTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
- R.dimen.all_apps_grid_section_text_size));
- mSectionTextPaint.setColor(Utilities.getColorAccent(launcher));
}
public static boolean isDividerViewType(int viewType) {
@@ -421,33 +224,15 @@
}
/**
- * Notifies the adapter of the background padding so that it can draw things correctly in the
- * item decorator.
- */
- public void updateBackgroundPadding(Rect padding) {
- mBackgroundPadding.set(padding);
- }
-
- /**
* Returns the grid layout manager.
*/
public GridLayoutManager getLayoutManager() {
return mGridLayoutMgr;
}
- /**
- * Returns the item decoration for the recycler view.
- */
- public RecyclerView.ItemDecoration getItemDecoration() {
- // We don't draw any headers when we are uncomfortably dense
- return mItemDecoration;
- }
-
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
- case VIEW_TYPE_SECTION_BREAK:
- return new ViewHolder(new View(parent.getContext()));
case VIEW_TYPE_ICON:
/* falls through */
case VIEW_TYPE_PREDICTION_ICON: {
@@ -499,26 +284,26 @@
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON: {
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
- BubbleTextView icon = (BubbleTextView) holder.mContent;
+ BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.applyFromApplicationInfo(info);
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
}
case VIEW_TYPE_PREDICTION_ICON: {
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
- BubbleTextView icon = (BubbleTextView) holder.mContent;
+ BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.applyFromApplicationInfo(info);
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
}
case VIEW_TYPE_EMPTY_SEARCH:
- TextView emptyViewText = (TextView) holder.mContent;
+ TextView emptyViewText = (TextView) holder.itemView;
emptyViewText.setText(mEmptySearchMessage);
emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
Gravity.START | Gravity.CENTER_VERTICAL);
break;
case VIEW_TYPE_SEARCH_MARKET:
- TextView searchView = (TextView) holder.mContent;
+ TextView searchView = (TextView) holder.itemView;
if (mMarketSearchIntent != null) {
searchView.setVisibility(View.VISIBLE);
} else {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 0173847..ab34287 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -101,7 +101,6 @@
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, 1);
- pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, approxRows);
}
/**
@@ -116,21 +115,21 @@
// Icons
BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_ICON).mContent;
+ AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
int iconHeight = icon.getLayoutParams().height;
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
// Search divider
View searchDivider = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).mContent;
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).itemView;
searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
int searchDividerHeight = searchDivider.getMeasuredHeight();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
// Generic dividers
View divider = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).mContent;
+ AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).itemView;
divider.measure(widthMeasureSpec, heightMeasureSpec);
int dividerHeight = divider.getMeasuredHeight();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
@@ -138,18 +137,15 @@
// Search views
View emptySearch = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).mContent;
+ AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).itemView;
emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
emptySearch.getMeasuredHeight());
View searchMarket = adapter.onCreateViewHolder(this,
- AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).mContent;
+ AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).itemView;
searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
searchMarket.getMeasuredHeight());
-
- // Section breaks
- mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, 0);
}
/**
@@ -166,27 +162,10 @@
}
}
- /**
- * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
- * background bounds.
- */
- @Override
- protected void dispatchDraw(Canvas canvas) {
- // Clip to ensure that we don't draw the overscroll effect beyond the background bounds
- canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
- getWidth() - mBackgroundPadding.right,
- getHeight() - mBackgroundPadding.bottom);
- super.dispatchDraw(canvas);
- }
-
@Override
public void onDraw(Canvas c) {
// Draw the background
if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
- c.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
- getWidth() - mBackgroundPadding.right,
- getHeight() - mBackgroundPadding.bottom);
-
mEmptySearchBackground.draw(c);
}
@@ -299,14 +278,14 @@
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
// Skip early if, there no child laid out in the container.
int scrollY = getCurrentScrollY();
if (scrollY < 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
@@ -314,7 +293,7 @@
int availableScrollBarHeight = getAvailableScrollBarHeight();
int availableScrollHeight = getAvailableScrollHeight();
if (availableScrollHeight <= 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
@@ -323,11 +302,10 @@
// Calculate the current scroll position, the scrollY of the recycler view accounts
// for the view padding, while the scrollBarY is drawn right up to the background
// padding (ignoring padding)
- int scrollBarX = getScrollBarX();
- int scrollBarY = mBackgroundPadding.top +
- (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+ int scrollBarY = (int)
+ (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
- int thumbScrollY = mScrollbar.getThumbOffset().y;
+ int thumbScrollY = mScrollbar.getThumbOffsetY();
int diffScrollY = scrollBarY - thumbScrollY;
if (diffScrollY * dy > 0f) {
// User is scrolling in the same direction the thumb needs to catch up to the
@@ -344,7 +322,7 @@
thumbScrollY += Math.min(offset, diffScrollY);
}
thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
- mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ mScrollbar.setThumbOffsetY(thumbScrollY);
if (scrollBarY == thumbScrollY) {
mScrollbar.reattachThumbToScroll();
}
@@ -352,7 +330,7 @@
// User is scrolling in an opposite direction to the direction that the thumb
// needs to catch up to the scroll position. Do nothing except for updating
// the scroll bar x to match the thumb width.
- mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+ mScrollbar.setThumbOffsetY(thumbScrollY);
}
}
} else {
@@ -416,8 +394,8 @@
}
@Override
- protected int getVisibleHeight() {
- return super.getVisibleHeight()
+ protected int getScrollbarTrackHeight() {
+ return super.getScrollbarTrackHeight()
- Launcher.getLauncher(getContext()).getDragLayer().getInsets().bottom;
}
@@ -429,7 +407,7 @@
protected int getAvailableScrollHeight() {
int paddedHeight = getCurrentScrollY(mApps.getAdapterItems().size(), 0);
int totalHeight = paddedHeight + getPaddingBottom();
- return totalHeight - getVisibleHeight();
+ return totalHeight - getScrollbarTrackHeight();
}
/**
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index b5afb2b..6587ad7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -21,6 +21,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
@@ -33,7 +34,7 @@
* A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
* is launching.
*/
-public class AllAppsRecyclerViewContainerView extends FrameLayout
+public class AllAppsRecyclerViewContainerView extends RelativeLayout
implements BubbleTextShadowHandler {
private final ClickShadowView mTouchFeedbackView;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b129bb0..adfad08 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -108,7 +108,7 @@
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = false;
if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) {
@@ -174,7 +174,7 @@
}
@Override
- public boolean onTouchEvent(MotionEvent ev) {
+ public boolean onControllerTouchEvent(MotionEvent ev) {
return mDetector.onTouchEvent(ev);
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 173065b..8b7a6ba 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -23,8 +23,8 @@
import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.model.AppNameComparator;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LabelComparator;
import java.util.ArrayList;
import java.util.Collections;
@@ -49,18 +49,6 @@
private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
/**
- * Info about a section in the alphabetic list
- */
- public static class SectionInfo {
- // The number of applications in this section
- public int numApps;
- // The section break AdapterItem for this section
- public AdapterItem sectionBreakItem;
- // The first app AdapterItem for this section
- public AdapterItem firstAppItem;
- }
-
- /**
* Info about a fast scroller section, depending if sections are merged, the fast scroller
* sections will not be the same set as the section headers.
*/
@@ -87,16 +75,10 @@
// The type of this item
public int viewType;
- /** Section & App properties */
- // The section for this item
- public SectionInfo sectionInfo;
-
/** App-only properties */
// The section name of this app. Note that there can be multiple items with different
// sectionNames in the same section
public String sectionName = null;
- // The index of this app in the section
- public int sectionAppIndex = -1;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
@@ -106,30 +88,19 @@
// The index of this app not including sections
public int appIndex = -1;
- public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
- AdapterItem item = new AdapterItem();
- item.viewType = AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK;
- item.position = pos;
- item.sectionInfo = section;
- section.sectionBreakItem = item;
- return item;
- }
-
- public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
- AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
+ public static AdapterItem asPredictedApp(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
+ AdapterItem item = asApp(pos, sectionName, appInfo, appIndex);
item.viewType = AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON;
return item;
}
- public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
- int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+ int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
item.position = pos;
- item.sectionInfo = section;
item.sectionName = sectionName;
- item.sectionAppIndex = sectionAppIndex;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
@@ -149,7 +120,7 @@
return item;
}
- public static AdapterItem asSearchDivder(int pos) {
+ public static AdapterItem asSearchDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER;
item.position = pos;
@@ -171,14 +142,6 @@
}
}
- /**
- * Common interface for different merging strategies.
- */
- public interface MergeAlgorithm {
- boolean continueMerging(SectionInfo section, SectionInfo withSection,
- int sectionAppCount, int numAppsPerRow, int mergeCount);
- }
-
private Launcher mLauncher;
// The set of apps from the system not including predictions
@@ -189,8 +152,6 @@
private List<AppInfo> mFilteredApps = new ArrayList<>();
// The current set of adapter items
private List<AdapterItem> mAdapterItems = new ArrayList<>();
- // The set of sections for the apps with the current filter
- private List<SectionInfo> mSections = new ArrayList<>();
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
@@ -202,8 +163,7 @@
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
private AllAppsGridAdapter mAdapter;
private AlphabeticIndexCompat mIndexer;
- private AppNameComparator mAppNameComparator;
- private MergeAlgorithm mMergeAlgorithm;
+ private AppInfoComparator mAppNameComparator;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
private int mNumAppRowsInAdapter;
@@ -211,17 +171,15 @@
public AlphabeticalAppsList(Context context) {
mLauncher = Launcher.getLauncher(context);
mIndexer = new AlphabeticIndexCompat(context);
- mAppNameComparator = new AppNameComparator(context);
+ mAppNameComparator = new AppInfoComparator(context);
}
/**
* Sets the number of apps per row.
*/
- public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow,
- MergeAlgorithm mergeAlgorithm) {
+ public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
mNumAppsPerRow = numAppsPerRow;
mNumPredictedAppsPerRow = numPredictedAppsPerRow;
- mMergeAlgorithm = mergeAlgorithm;
updateAdapterItems();
}
@@ -241,13 +199,6 @@
}
/**
- * Returns sections of all the current filtered applications.
- */
- public List<SectionInfo> getSections() {
- return mSections;
- }
-
- /**
* Returns fast scroller sections of all the current filtered applications.
*/
public List<FastScrollSectionInfo> getFastScrollerSections() {
@@ -354,17 +305,16 @@
// Sort the list of apps
mApps.clear();
mApps.addAll(mComponentToAppMap.values());
- Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
+ Collections.sort(mApps, mAppNameComparator);
// As a special case for some languages (currently only Simplified Chinese), we may need to
// coalesce sections
Locale curLocale = mLauncher.getResources().getConfiguration().locale;
- TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
if (localeRequiresSectionSorting) {
- // Compute the section headers. We use a TreeMap with the section name comparator to
+ // Compute the section headers. We use a TreeMap with the section name comparator to
// ensure that the sections are ordered when we iterate over it later
- sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
+ TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
for (AppInfo info : mApps) {
// Add the section to the cache
String sectionName = getAndUpdateCachedSectionName(info.title);
@@ -379,13 +329,10 @@
}
// Add each of the section apps to the list in order
- List<AppInfo> allApps = new ArrayList<>(mApps.size());
- for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
- allApps.addAll(entry.getValue());
- }
-
mApps.clear();
- mApps.addAll(allApps);
+ for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+ mApps.addAll(entry.getValue());
+ }
} else {
// Just compute the section headers for use below
for (AppInfo info : mApps) {
@@ -403,7 +350,6 @@
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
private void updateAdapterItems() {
- SectionInfo lastSectionInfo = null;
String lastSectionName = null;
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
@@ -413,7 +359,6 @@
mFilteredApps.clear();
mFastScrollerSections.clear();
mAdapterItems.clear();
- mSections.clear();
if (DEBUG_PREDICTIONS) {
if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
@@ -429,7 +374,7 @@
}
// Add the search divider
- mAdapterItems.add(AdapterItem.asSearchDivder(position++));
+ mAdapterItems.add(AdapterItem.asSearchDivider(position++));
// Process the predicted app components
mPredictedApps.clear();
@@ -451,19 +396,14 @@
if (!mPredictedApps.isEmpty()) {
// Add a section for the predictions
- lastSectionInfo = new SectionInfo();
lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
- AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
- mSections.add(lastSectionInfo);
mFastScrollerSections.add(lastFastScrollerSectionInfo);
- mAdapterItems.add(sectionItem);
// Add the predicted app items
for (AppInfo info : mPredictedApps) {
- AdapterItem appItem = AdapterItem.asPredictedApp(position++, lastSectionInfo,
- "", lastSectionInfo.numApps++, info, appIndex++);
- if (lastSectionInfo.firstAppItem == null) {
- lastSectionInfo.firstAppItem = appItem;
+ AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
+ appIndex++);
+ if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
}
mAdapterItems.add(appItem);
@@ -480,25 +420,15 @@
String sectionName = getAndUpdateCachedSectionName(info.title);
// Create a new section if the section names do not match
- if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
+ if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
- lastSectionInfo = new SectionInfo();
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
- mSections.add(lastSectionInfo);
mFastScrollerSections.add(lastFastScrollerSectionInfo);
-
- // Create a new section item to break the flow of items in the list
- if (!hasFilter()) {
- AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
- mAdapterItems.add(sectionItem);
- }
}
// Create an app item
- AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
- lastSectionInfo.numApps++, info, appIndex++);
- if (lastSectionInfo.firstAppItem == null) {
- lastSectionInfo.firstAppItem = appItem;
+ AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
+ if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
}
mAdapterItems.add(appItem);
@@ -515,9 +445,6 @@
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
- // Merge multiple sections together as requested by the merge strategy for this device
- mergeSections();
-
if (mNumAppsPerRow != 0) {
// Update the number of rows in the adapter after we do all the merging (otherwise, we
// would have to shift the values again)
@@ -594,61 +521,6 @@
}
/**
- * Merges multiple sections to reduce visual raggedness.
- */
- private void mergeSections() {
- // Ignore merging until we have an algorithm and a valid row size
- if (mMergeAlgorithm == null || mNumAppsPerRow == 0) {
- return;
- }
-
- // Go through each section and try and merge some of the sections
- if (!hasFilter()) {
- int sectionAppCount = 0;
- for (int i = 0; i < mSections.size() - 1; i++) {
- SectionInfo section = mSections.get(i);
- sectionAppCount = section.numApps;
- int mergeCount = 1;
-
- // Merge rows based on the current strategy
- while (i < (mSections.size() - 1) &&
- mMergeAlgorithm.continueMerging(section, mSections.get(i + 1),
- sectionAppCount, mNumAppsPerRow, mergeCount)) {
- SectionInfo nextSection = mSections.remove(i + 1);
-
- // Remove the next section break
- mAdapterItems.remove(nextSection.sectionBreakItem);
- int pos = mAdapterItems.indexOf(section.firstAppItem);
-
- // Point the section for these new apps to the merged section
- int nextPos = pos + section.numApps;
- for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
- AdapterItem item = mAdapterItems.get(j);
- item.sectionInfo = section;
- item.sectionAppIndex += section.numApps;
- }
-
- // Update the following adapter items of the removed section item
- pos = mAdapterItems.indexOf(nextSection.firstAppItem);
- for (int j = pos; j < mAdapterItems.size(); j++) {
- AdapterItem item = mAdapterItems.get(j);
- item.position--;
- }
- section.numApps += nextSection.numApps;
- sectionAppCount += nextSection.numApps;
-
- if (DEBUG) {
- Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName +
- " to " + section.firstAppItem.sectionName +
- " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow));
- }
- mergeCount++;
- }
- }
- }
- }
-
- /**
* Returns the cached section name for the given title, recomputing and updating the cache if
* the title has no cached section name.
*/
diff --git a/src/com/android/launcher3/model/AbstractUserComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
similarity index 61%
rename from src/com/android/launcher3/model/AbstractUserComparator.java
rename to src/com/android/launcher3/allapps/AppInfoComparator.java
index bd28560..1f5fece 100644
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -13,36 +13,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.model;
+package com.android.launcher3.allapps;
import android.content.Context;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.LabelComparator;
import java.util.Comparator;
/**
* A comparator to arrange items based on user profiles.
*/
-public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
+public class AppInfoComparator implements Comparator<AppInfo> {
private final UserManagerCompat mUserManager;
private final UserHandleCompat mMyUser;
+ private final LabelComparator mLabelComparator;
- public AbstractUserComparator(Context context) {
+ public AppInfoComparator(Context context) {
mUserManager = UserManagerCompat.getInstance(context);
mMyUser = UserHandleCompat.myUserHandle();
+ mLabelComparator = new LabelComparator();
}
@Override
- public int compare(T lhs, T rhs) {
- if (mMyUser.equals(lhs.user)) {
+ public int compare(AppInfo a, AppInfo b) {
+ // Order by the title in the current locale
+ int result = mLabelComparator.compare(a.title.toString(), b.title.toString());
+ if (result != 0) {
+ return result;
+ }
+
+ // If labels are same, compare component names
+ result = a.componentName.compareTo(b.componentName);
+ if (result != 0) {
+ return result;
+ }
+
+ if (mMyUser.equals(a.user)) {
return -1;
} else {
- Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
- Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
+ Long aUserSerial = mUserManager.getSerialNumberForUser(a.user);
+ Long bUserSerial = mUserManager.getSerialNumberForUser(b.user);
return aUserSerial.compareTo(bUserSerial);
}
}
diff --git a/src/com/android/launcher3/allapps/HeaderElevationController.java b/src/com/android/launcher3/allapps/HeaderElevationController.java
index ce9837c..e79e5c7 100644
--- a/src/com/android/launcher3/allapps/HeaderElevationController.java
+++ b/src/com/android/launcher3/allapps/HeaderElevationController.java
@@ -14,6 +14,7 @@
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
/**
* Helper class for controlling the header elevation in response to RecyclerView scroll.
@@ -84,7 +85,7 @@
public ControllerVL(View header) {
mHeader = header;
- Resources res = mHeader.getContext().getResources();
+ final Resources res = mHeader.getContext().getResources();
mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
@@ -101,11 +102,8 @@
final int right = left + view.getWidth();
final int bottom = view.getBottom();
- outline.setRect(
- left - (int) mMaxElevation,
- top - (int) mMaxElevation,
- right + (int) mMaxElevation,
- bottom);
+ final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics());
+ outline.setRect(left - offset, top - offset, right + offset, bottom);
}
};
mHeader.setOutlineProvider(vop);
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index ab2b6ed..96e1299 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -230,7 +230,7 @@
private void reportDragEnd() {
if (DBG) {
- Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
+ Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
mDisplacementY, mVelocity));
}
mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
new file mode 100644
index 0000000..42706ff
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimationLayerSet.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.View;
+
+import java.util.HashSet;
+
+/**
+ * Helper class to automatically build view hardware layers for the duration of an animation.
+ */
+public class AnimationLayerSet extends AnimatorListenerAdapter {
+
+ private final HashSet<View> mViews = new HashSet<>();
+
+ public void addView(View v) {
+ mViews.add(v);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Enable all necessary layers
+ for (View v : mViews) {
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
+ v.buildLayer();
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (View v : mViews) {
+ v.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index c7a529d..84e82e3 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -23,7 +23,7 @@
BaseIndex index = null;
try {
- if (Utilities.isNycOrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT) {
index = new AlphabeticIndexVN(context);
}
} catch (Exception e) {
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
index 0bc9588..65af4ea 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -17,9 +17,7 @@
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 {
@@ -33,11 +31,4 @@
public abstract Drawable getIcon(int density);
public abstract ApplicationInfo getApplicationInfo();
public abstract long getFirstInstallTime();
-
- /**
- * 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/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 4aa667e..948471c 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -54,7 +54,7 @@
HashMap<String, Integer> activePackages = new HashMap<>();
UserHandleCompat user = UserHandleCompat.myUserHandle();
for (SessionInfo info : mInstaller.getAllSessions()) {
- addSessionInfoToCahce(info, user);
+ addSessionInfoToCache(info, user);
if (info.getAppPackageName() != null) {
activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100));
mActiveSessions.put(info.getSessionId(), info.getAppPackageName());
@@ -63,7 +63,7 @@
return activePackages;
}
- @Thunk void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
+ @Thunk void addSessionInfoToCache(SessionInfo info, UserHandleCompat user) {
String packageName = info.getAppPackageName();
if (packageName != null) {
mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
@@ -124,7 +124,7 @@
private void pushSessionDisplayToLauncher(int sessionId) {
SessionInfo session = mInstaller.getSessionInfo(sessionId);
if (session != null && session.getAppPackageName() != null) {
- addSessionInfoToCahce(session, UserHandleCompat.myUserHandle());
+ addSessionInfoToCache(session, UserHandleCompat.myUserHandle());
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a5f8dd2..b40eaa2 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -32,9 +32,9 @@
public static UserManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
sInstance = new UserManagerCompatVNMr1(context.getApplicationContext());
- } else if (Utilities.isNycOrAbove()) {
+ } else if (Utilities.ATLEAST_NOUGAT) {
sInstance = new UserManagerCompatVN(context.getApplicationContext());
} else if (Utilities.ATLEAST_MARSHMALLOW) {
sInstance = new UserManagerCompatVM(context.getApplicationContext());
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
index 156941a..e968f36 100644
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
@@ -70,7 +70,7 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
// TODO: Probably log something
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 6eb7dcc..67ef5fc 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -131,7 +131,7 @@
protected final int mFlingToDeleteThresholdVelocity;
private VelocityTracker mVelocityTracker;
- private boolean mIsDragDeferred;
+ private boolean mIsInPreDrag;
/**
* Interface to receive notifications when a drag starts or stops
@@ -230,13 +230,14 @@
mDragObject = new DropTarget.DragObject();
- mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0);
+ mIsInPreDrag = mOptions.preDragCondition != null
+ && !mOptions.preDragCondition.shouldStartDrag(0);
final Resources res = mLauncher.getResources();
final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND
? res.getDimensionPixelSize(R.dimen.dragViewScale)
- : mIsDragDeferred
- ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale)
+ : mIsInPreDrag
+ ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale)
: 0f;
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
registrationY, initialDragViewScale, scaleDps);
@@ -271,10 +272,10 @@
dragView.show(mMotionDownX, mMotionDownY);
mDistanceSinceScroll = 0;
- if (!mIsDragDeferred) {
- startDeferredDrag();
- } else {
- mOptions.deferDragCondition.onDeferredDragStart();
+ if (!mIsInPreDrag) {
+ callOnDragStart();
+ } else if (mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragStart();
}
mLastTouch[0] = mMotionDownX;
@@ -284,16 +285,14 @@
return dragView;
}
- public boolean isDeferringDrag() {
- return mIsDragDeferred;
- }
-
- public void startDeferredDrag() {
+ private void callOnDragStart() {
for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragStart(mDragObject, mOptions);
}
- mOptions.deferDragCondition.onDragStart();
- mIsDragDeferred = false;
+ if (mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragEnd(true /* dragStarted*/);
+ }
+ mIsInPreDrag = false;
}
/**
@@ -329,7 +328,9 @@
mDragObject.deferDragViewCleanupPostAnimation = false;
mDragObject.cancelled = true;
mDragObject.dragComplete = true;
- mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+ if (!mIsInPreDrag) {
+ mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+ }
}
endDrag();
}
@@ -350,28 +351,54 @@
private void endDrag() {
if (isDragging()) {
mDragDriver = null;
- mOptions = null;
clearScrollRunnable();
boolean isDeferred = false;
if (mDragObject.dragView != null) {
isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
if (!isDeferred) {
mDragObject.dragView.remove();
+ } else if (mIsInPreDrag) {
+ animateDragViewToOriginalPosition(null, null, -1);
}
mDragObject.dragView = null;
}
// Only end the drag if we are not deferred
if (!isDeferred) {
- for (DragListener listener : new ArrayList<>(mListeners)) {
- listener.onDragEnd();
- }
+ callOnDragEnd();
}
}
releaseVelocityTracker();
}
+ public void animateDragViewToOriginalPosition(final Runnable onComplete,
+ final View originalIcon, int duration) {
+ Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (originalIcon != null) {
+ originalIcon.setVisibility(View.VISIBLE);
+ }
+ if (onComplete != null) {
+ onComplete.run();
+ }
+ }
+ };
+ mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
+ }
+
+ private void callOnDragEnd() {
+ if (mIsInPreDrag && mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragEnd(false /* dragStarted*/);
+ }
+ mIsInPreDrag = false;
+ mOptions = null;
+ for (DragListener listener : new ArrayList<>(mListeners)) {
+ listener.onDragEnd();
+ }
+ }
+
/**
* This only gets called as a result of drag view cleanup being deferred in endDrag();
*/
@@ -380,9 +407,7 @@
if (mDragObject.deferDragViewCleanupPostAnimation) {
// If we skipped calling onDragEnd() before, do it now
- for (DragListener listener : new ArrayList<>(mListeners)) {
- listener.onDragEnd();
- }
+ callOnDragEnd();
}
}
@@ -456,7 +481,7 @@
/**
* Call this from a drag source view.
*/
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (mOptions != null && mOptions.isAccessibleDrag) {
return false;
}
@@ -536,9 +561,9 @@
mLastTouch[1] = y;
checkScrollState(x, y);
- if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag(
- Math.hypot(x - mMotionDownX, y - mMotionDownY))) {
- startDeferredDrag();
+ if (mIsInPreDrag && mOptions.preDragCondition != null
+ && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
+ callOnDragStart();
}
}
@@ -604,7 +629,7 @@
/**
* Call this from a drag source view.
*/
- public boolean onTouchEvent(MotionEvent ev) {
+ public boolean onControllerTouchEvent(MotionEvent ev) {
if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
return false;
}
@@ -701,7 +726,7 @@
(vec1.length() * vec2.length()));
}
- void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
+ private void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
final int[] coordinates = mCoordinatesTemp;
mDragObject.x = coordinates[0];
@@ -727,18 +752,17 @@
if (dropTarget.acceptDrop(mDragObject)) {
if (flingVel != null) {
dropTarget.onFlingToDelete(mDragObject, flingVel);
- } else {
+ } else if (!mIsInPreDrag) {
dropTarget.onDrop(mDragObject);
}
accepted = true;
}
}
final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
- mDragObject.dragSource.onDropCompleted(
- dropTargetAsView, mDragObject, flingVel != null, accepted);
mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
- if (mIsDragDeferred) {
- mOptions.deferDragCondition.onDropBeforeDeferredDrag();
+ if (!mIsInPreDrag) {
+ mDragObject.dragSource.onDropCompleted(
+ dropTargetAsView, mDragObject, flingVel != null, accepted);
}
}
@@ -760,7 +784,7 @@
dropCoordinates[0] = x;
dropCoordinates[1] = y;
- mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
return target;
}
@@ -768,7 +792,7 @@
return null;
}
- public void setDragScoller(DragScroller scroller) {
+ public void setDragScroller(DragScroller scroller) {
mDragScroller = scroller;
}
@@ -777,7 +801,7 @@
}
/**
- * Sets the drag listner which will be notified when a drag starts or ends.
+ * Sets the drag listener which will be notified when a drag starts or ends.
*/
public void addDragListener(DragListener l) {
mListeners.add(l);
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 4db8c07..d0c8e16 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -92,7 +92,7 @@
public static DragDriver create(Context context, DragController dragController,
DragObject dragObject, DragOptions options) {
- if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) {
+ if (Utilities.ATLEAST_NOUGAT && options.systemDndStartPoint != null) {
return new SystemDragDriver(dragController, context, dragObject);
} else {
return new InternalDragDriver(dragController);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 016347b..9de4452 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -36,9 +36,9 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.DragEvent;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -49,12 +49,12 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTargetBar;
+import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetHostView;
import com.android.launcher3.PinchToOverviewListener;
@@ -68,11 +68,9 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
-import java.net.URISyntaxException;
import java.util.ArrayList;
/**
@@ -90,11 +88,9 @@
@Thunk DragController mDragController;
- private int mXDown, mYDown;
private Launcher mLauncher;
// Variables relating to resizing widgets
- private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>();
private final boolean mIsRtl;
private AppWidgetResizeFrame mCurrentResizeFrame;
@@ -149,10 +145,12 @@
setChildrenDrawingOrderEnabled(true);
final Resources res = getResources();
- mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
- mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
- mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
- mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
+ if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
+ mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
+ mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
+ mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
+ mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
+ }
mIsRtl = Utilities.isRtl(res);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
}
@@ -183,18 +181,13 @@
}
public boolean isEventOverPageIndicator(MotionEvent ev) {
- getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect);
- return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+ return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
}
public boolean isEventOverHotseat(MotionEvent ev) {
return isEventOverView(mLauncher.getHotseat(), ev);
}
- private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
- return isEventOverView(folder.getEditTextRegion(), ev);
- }
-
private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
return isEventOverView(folder, ev);
}
@@ -209,62 +202,27 @@
}
private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
- Rect hitRect = new Rect();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
- for (AppWidgetResizeFrame child: mResizeFrames) {
- child.getHitRect(hitRect);
- if (hitRect.contains(x, y)) {
- if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
- mCurrentResizeFrame = child;
- mXDown = x;
- mYDown = y;
- requestDisallowInterceptTouchEvent(true);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
+ if (topView != null && intercept) {
+ ExtendedEditText textView = topView.getActiveTextView();
+ if (textView != null) {
+ if (!isEventOverView(textView, ev)) {
+ textView.dispatchBackKey();
return true;
}
- }
- }
-
- // Remove the shortcuts container when touching outside of it.
- DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer();
- if (deepShortcutsContainer != null) {
- if (isEventOverView(deepShortcutsContainer, ev)) {
- // Let the container handle the event.
- return false;
- } else {
+ } else if (!isEventOverView(topView, ev)) {
if (isInAccessibleDrag()) {
// Do not close the container if in drag and drop.
if (!isEventOverDropTargetBar(ev)) {
return true;
}
} else {
- mLauncher.closeShortcutsContainer();
+ topView.close(true);
+
// We let touches on the original icon go through so that users can launch
// the app with one tap if they don't find a shortcut they want.
- return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
- }
- }
- }
-
- Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
- if (currentFolder != null && intercept) {
- if (currentFolder.isEditingName()) {
- if (!isEventOverFolderTextRegion(currentFolder, ev)) {
- currentFolder.dismissEditingName();
- return true;
- }
- }
-
- if (!isEventOverFolder(currentFolder, ev)) {
- if (isInAccessibleDrag()) {
- // Do not close the folder if in drag and drop.
- if (!isEventOverDropTargetBar(ev)) {
- return true;
- }
- } else {
- mLauncher.closeFolder();
- return true;
+ View extendedTouch = topView.getExtendedTouchView();
+ return extendedTouch == null || !isEventOverView(extendedTouch, ev);
}
}
}
@@ -289,21 +247,27 @@
}
mTouchCompleteListener = null;
}
- clearAllResizeFrames();
-
mActiveController = null;
- if (mDragController.onInterceptTouchEvent(ev)) {
+ if (mCurrentResizeFrame != null
+ && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
+ mActiveController = mCurrentResizeFrame;
+ return true;
+ } else {
+ clearResizeFrame();
+ }
+
+ if (mDragController.onControllerInterceptTouchEvent(ev)) {
mActiveController = mDragController;
return true;
}
- if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) {
mActiveController = mAllAppsController;
return true;
}
- if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
+ if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
// Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
mActiveController = mPinchListener;
return true;
@@ -316,7 +280,7 @@
if (mLauncher == null || mLauncher.getWorkspace() == null) {
return false;
}
- Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ Folder currentFolder = Folder.getOpen(mLauncher);
if (currentFolder == null) {
return false;
} else {
@@ -366,7 +330,7 @@
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
// Shortcuts can appear above folder
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
if (child == topView) {
return super.onRequestSendAccessibilityEvent(child, event);
@@ -383,7 +347,7 @@
@Override
public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
// Only add the top view as a child for accessibility when it is open
childrenForAccessibility.add(topView);
@@ -405,12 +369,8 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
- boolean handled = false;
int action = ev.getAction();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
-
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, false)) {
return true;
@@ -422,29 +382,15 @@
mTouchCompleteListener = null;
}
- if (mCurrentResizeFrame != null) {
- handled = true;
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
- mCurrentResizeFrame.onTouchUp();
- mCurrentResizeFrame = null;
- }
- }
- if (handled) return true;
if (mActiveController != null) {
- return mActiveController.onTouchEvent(ev);
+ return mActiveController.onControllerTouchEvent(ev);
}
return false;
}
@TargetApi(Build.VERSION_CODES.N)
private void handleSystemDragStart(DragEvent event) {
- if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) {
+ if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.ATLEAST_NOUGAT) {
return;
}
if (mLauncher.isWorkspaceLocked()) {
@@ -534,8 +480,8 @@
/**
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
*/
- public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
- return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
+ public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
+ Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
}
public void getViewRectRelativeToSelf(View v, Rect r) {
@@ -556,7 +502,7 @@
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
// Consume the unhandled move if a container is open, to avoid switching pages underneath.
- boolean isContainerOpen = mLauncher.getTopFloatingView() != null;
+ boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null;
return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
}
@@ -645,36 +591,24 @@
}
}
- public void clearAllResizeFrames() {
- if (mResizeFrames.size() > 0) {
- for (AppWidgetResizeFrame frame: mResizeFrames) {
- frame.commitResize();
- removeView(frame);
- }
- mResizeFrames.clear();
+ public void clearResizeFrame() {
+ if (mCurrentResizeFrame != null) {
+ mCurrentResizeFrame.commitResize();
+ removeView(mCurrentResizeFrame);
+ mCurrentResizeFrame = null;
}
}
- public boolean hasResizeFrames() {
- return mResizeFrames.size() > 0;
- }
+ public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+ clearResizeFrame();
- public boolean isWidgetBeingResized() {
- return mCurrentResizeFrame != null;
- }
+ mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher)
+ .inflate(R.layout.app_widget_resize_frame, this, false);
+ mCurrentResizeFrame.setupForWidget(widget, cellLayout, this);
+ ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true;
- public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
- CellLayout cellLayout) {
- AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
- widget, cellLayout, this);
-
- LayoutParams lp = new LayoutParams(-1, -1);
- lp.customPosition = true;
-
- addView(resizeFrame, lp);
- mResizeFrames.add(resizeFrame);
-
- resizeFrame.snapToWidget(false);
+ addView(mCurrentResizeFrame);
+ mCurrentResizeFrame.snapToWidget(false);
}
public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
@@ -1077,7 +1011,7 @@
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
return topView.requestFocus(direction, previouslyFocusedRect);
} else {
@@ -1087,7 +1021,7 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- View topView = mLauncher.getTopFloatingView();
+ View topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
topView.addFocusables(views, direction);
} else {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index dbf46f3..906855a 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -17,6 +17,8 @@
package com.android.launcher3.dragndrop;
import android.graphics.Point;
+import android.support.annotation.CallSuper;
+import android.view.View;
/**
* Set of options to control the drag and drop behavior.
@@ -29,8 +31,8 @@
/** Specifies the start location for the system DnD, null when using internal DnD */
public Point systemDndStartPoint = null;
- /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */
- public DeferDragCondition deferDragCondition = new DeferDragCondition();
+ /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
+ public PreDragCondition preDragCondition = null;
/**
* Specifies a condition that must be met before DragListener#onDragStart() is called.
@@ -38,34 +40,26 @@
* DragController#startDrag().
*
* This condition can be overridden, and callbacks are provided for the following cases:
- * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()).
- * - The drag ends before the condition is met (onDropBeforeDeferredDrag()).
- * - The condition is met (onDragStart()).
+ * - The pre-drag starts, but onDragStart() is deferred (onPreDragStart()).
+ * - The pre-drag ends before the condition is met (onPreDragEnd(false)).
+ * - The actual drag starts when the condition is met (onPreDragEnd(true)).
*/
- public static class DeferDragCondition {
- public boolean shouldStartDeferredDrag(double distanceDragged) {
- return true;
- }
+ public interface PreDragCondition {
+
+ public boolean shouldStartDrag(double distanceDragged);
/**
- * The drag has started, but onDragStart() is deferred.
- * This happens when shouldStartDeferredDrag() returns true.
+ * The pre-drag has started, but onDragStart() is
+ * deferred until shouldStartDrag() returns true.
*/
- public void onDeferredDragStart() {
- // Do nothing.
- }
+ void onPreDragStart();
/**
- * User dropped before the deferred condition was met,
- * i.e. before shouldStartDeferredDrag() returned true.
+ * The pre-drag has ended. This gets called at the same time as onDragStart()
+ * if the condition is met, otherwise at the same time as onDragEnd().
+ * @param dragStarted Whether the pre-drag ended because the actual drag started.
+ * This will be true if the condition was met, otherwise false.
*/
- public void onDropBeforeDeferredDrag() {
- // Do nothing
- }
-
- /** onDragStart() has been called, now we are in a normal drag. */
- public void onDragStart() {
- // Do nothing
- }
+ void onPreDragEnd(boolean dragStarted);
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8a2ae94..22e077b 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -23,7 +23,6 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -38,11 +37,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Thunk;
-
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Thunk;
import java.util.Arrays;
@@ -57,6 +54,8 @@
@Thunk Paint mPaint;
private final int mRegistrationX;
private final int mRegistrationY;
+ private final float mInitialScale;
+ private final int[] mTempLoc = new int[2];
private Point mDragVisualizeOffset = null;
private Rect mDragRegion = null;
@@ -138,6 +137,8 @@
mRegistrationX = registrationX;
mRegistrationY = registrationY;
+ mInitialScale = initialScale;
+
// Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
measure(ms, ms);
@@ -356,6 +357,13 @@
applyTranslation();
}
+ public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+ mTempLoc[0] = toTouchX - mRegistrationX;
+ mTempLoc[1] = toTouchY - mRegistrationY;
+ mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mInitialScale, mInitialScale,
+ DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+ }
+
public void animateShift(final int shiftX, final int shiftY) {
if (mAnim.isStarted()) {
return;
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
index 6b14be7..ac50332 100644
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
@@ -23,10 +23,10 @@
import android.graphics.Rect;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.HolographicOutlineHelper;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
/**
* Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
@@ -72,7 +72,7 @@
canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
- HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
+ HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
}
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
index 1369f60..1a127dc 100644
--- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java
+++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
@@ -16,24 +16,35 @@
package com.android.launcher3.dynamicui;
+import android.annotation.TargetApi;
import android.app.IntentService;
import android.app.WallpaperManager;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.support.v7.graphics.Palette;
+import android.util.Log;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import java.io.IOException;
+
/**
* Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}.
*/
public class ColorExtractionService extends IntentService {
+ private static final String TAG = "ColorExtractionService";
+
/** The fraction of the wallpaper to extract colors for use on the hotseat. */
private static final float HOTSEAT_FRACTION = 1f / 4;
@@ -45,32 +56,18 @@
protected void onHandleIntent(Intent intent) {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager);
+
ExtractedColors extractedColors = new ExtractedColors();
if (wallpaperManager.getWallpaperInfo() != null) {
// We can't extract colors from live wallpapers, so just use the default color always.
- extractedColors.updatePalette(null);
extractedColors.updateHotseatPalette(null);
} else {
- Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
- Palette palette = Palette.from(wallpaper).generate();
- extractedColors.updatePalette(palette);
// We extract colors for the hotseat and status bar separately,
// since they only consider part of the wallpaper.
- Palette hotseatPalette = Palette.from(wallpaper)
- .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)),
- wallpaper.getWidth(), wallpaper.getHeight())
- .clearFilters()
- .generate();
- extractedColors.updateHotseatPalette(hotseatPalette);
+ extractedColors.updateHotseatPalette(getHotseatPalette());
if (FeatureFlags.LIGHT_STATUS_BAR) {
- int statusBarHeight = getResources()
- .getDimensionPixelSize(R.dimen.status_bar_height);
- Palette statusBarPalette = Palette.from(wallpaper)
- .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight)
- .clearFilters()
- .generate();
- extractedColors.updateStatusBarPalette(statusBarPalette);
+ extractedColors.updateStatusBarPalette(getStatusBarPalette());
}
}
@@ -84,4 +81,63 @@
LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID,
null, extras);
}
+
+ @TargetApi(Build.VERSION_CODES.N)
+ private Palette getHotseatPalette() {
+ WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+ if (Utilities.ATLEAST_NOUGAT) {
+ try (ParcelFileDescriptor fd = wallpaperManager
+ .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
+ BitmapRegionDecoder decoder = BitmapRegionDecoder
+ .newInstance(fd.getFileDescriptor(), false);
+ int height = decoder.getHeight();
+ Rect decodeRegion = new Rect(0, (int) (height * (1f - HOTSEAT_FRACTION)),
+ decoder.getWidth(), height);
+ Bitmap bitmap = decoder.decodeRegion(decodeRegion, null);
+ decoder.recycle();
+ if (bitmap != null) {
+ return Palette.from(bitmap).clearFilters().generate();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
+ }
+ }
+
+ Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
+ return Palette.from(wallpaper)
+ .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)),
+ wallpaper.getWidth(), wallpaper.getHeight())
+ .clearFilters()
+ .generate();
+ }
+
+ @TargetApi(Build.VERSION_CODES.N)
+ private Palette getStatusBarPalette() {
+ WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+ int statusBarHeight = getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_height);
+
+ if (Utilities.ATLEAST_NOUGAT) {
+ try (ParcelFileDescriptor fd = wallpaperManager
+ .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
+ BitmapRegionDecoder decoder = BitmapRegionDecoder
+ .newInstance(fd.getFileDescriptor(), false);
+ Rect decodeRegion = new Rect(0, 0,
+ decoder.getWidth(), statusBarHeight);
+ Bitmap bitmap = decoder.decodeRegion(decodeRegion, null);
+ decoder.recycle();
+ if (bitmap != null) {
+ return Palette.from(bitmap).clearFilters().generate();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
+ }
+ }
+
+ Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
+ return Palette.from(wallpaper)
+ .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight)
+ .clearFilters()
+ .generate();
+ }
}
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
index 6a3011d..711508e 100644
--- a/src/com/android/launcher3/dynamicui/ExtractedColors.java
+++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java
@@ -113,34 +113,6 @@
}
/**
- * Updates colors based on the palette.
- * If the palette is null, the default color is used in all cases.
- */
- public void updatePalette(Palette palette) {
- if (palette == null) {
- for (int i = 0; i < NUM_COLOR_PROFILES; i++) {
- setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR);
- }
- } else {
- // We currently don't use any of the colors defined by the Palette API,
- // but this is how we would add them if we ever need them.
-
- // setColorAtIndex(ExtractedColors.VIBRANT_INDEX,
- // palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR));
- // setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX,
- // palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK));
- // setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX,
- // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
- // setColorAtIndex(ExtractedColors.MUTED_INDEX,
- // palette.getMutedColor(DEFAULT_COLOR));
- // setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX,
- // palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK));
- // setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX,
- // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
- }
- }
-
- /**
* The hotseat's color is defined as follows:
* - 12% black for super light wallpaper
* - 18% white for super dark
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
index 6dc0035..1cf5d55 100644
--- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java
+++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
@@ -16,18 +16,18 @@
package com.android.launcher3.dynamicui;
+import android.annotation.TargetApi;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
+import android.os.Build;
import android.support.v4.graphics.ColorUtils;
import android.support.v7.graphics.Palette;
import com.android.launcher3.Utilities;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.List;
/**
@@ -37,7 +37,6 @@
public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
- private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
private static final float MIN_CONTRAST_RATIO = 2f;
/**
@@ -63,7 +62,7 @@
}
private static boolean hasWallpaperIdChanged(Context context) {
- if (!Utilities.isNycOrAbove()) {
+ if (!Utilities.ATLEAST_NOUGAT) {
// TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here.
return false;
}
@@ -73,14 +72,10 @@
return wallpaperId != savedWallpaperId;
}
+ @TargetApi(Build.VERSION_CODES.N)
public static int getWallpaperId(WallpaperManager wallpaperManager) {
- // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly.
- try {
- Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class);
- return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM);
- } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
- return -1;
- }
+ return Utilities.ATLEAST_NOUGAT ?
+ wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1;
}
public static boolean isSuperLight(Palette p) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 698e5aa..53c12b5 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -46,9 +46,9 @@
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
@@ -71,8 +71,9 @@
import com.android.launcher3.UninstallDropTarget.DropTargetSource;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
-import com.android.launcher3.accessibility.AccessibileDragListenerAdapter;
+import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragLayer;
@@ -91,9 +92,10 @@
/**
* Represents a set of icons chosen by the user or generated by the system.
*/
-public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
+public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
- View.OnFocusChangeListener, DragListener, DropTargetSource {
+ View.OnFocusChangeListener, DragListener, DropTargetSource,
+ ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
/**
@@ -227,14 +229,7 @@
mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator);
mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
- mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
- @Override
- public boolean onBackKey() {
- // Close the activity on back key press
- doneEditingFolderName(true);
- return false;
- }
- });
+ mFolderName.setOnBackKeyListener(this);
mFolderName.setOnFocusChangeListener(this);
if (!Utilities.ATLEAST_MARSHMALLOW) {
@@ -281,17 +276,7 @@
public boolean onLongClick(View v) {
// Return if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return true;
- DragOptions dragOptions = new DragOptions();
- if (v instanceof BubbleTextView) {
- BubbleTextView icon = (BubbleTextView) v;
- if (icon.hasDeepShortcuts()) {
- DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
- if (dsc != null) {
- dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
- }
- }
- }
- return startDrag(v, dragOptions);
+ return startDrag(v, new DragOptions());
}
public boolean startDrag(View v, DragOptions options) {
@@ -307,7 +292,7 @@
mDragController.addDragListener(this);
if (options.isAccessibleDrag) {
- mDragController.addDragListener(new AccessibileDragListenerAdapter(
+ mDragController.addDragListener(new AccessibleDragListenerAdapter(
mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
@Override
@@ -367,12 +352,9 @@
});
}
- public void dismissEditingName() {
- mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- doneEditingFolderName(true);
- }
- public void doneEditingFolderName(boolean commit) {
+ @Override
+ public boolean onBackKey() {
mFolderName.setHint(sHintText);
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
@@ -380,30 +362,30 @@
mInfo.setTitle(newTitle);
LauncherModel.updateItemInDatabase(mLauncher, mInfo);
- if (commit) {
- Utilities.sendCustomAccessibilityEvent(
- this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.folder_renamed, newTitle));
- }
+ Utilities.sendCustomAccessibilityEvent(
+ this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ getContext().getString(R.string.folder_renamed, newTitle));
// This ensures that focus is gained every time the field is clicked, which selects all
// the text and brings up the soft keyboard if necessary.
mFolderName.clearFocus();
- Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
+ Selection.setSelection(mFolderName.getText(), 0, 0);
mIsEditingName = false;
+ return true;
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
- dismissEditingName();
+ mFolderName.dispatchBackKey();
return true;
}
return false;
}
- public View getEditTextRegion() {
- return mFolderName;
+ @Override
+ public ExtendedEditText getActiveTextView() {
+ return isEditingName() ? mFolderName : null;
}
/**
@@ -528,8 +510,33 @@
mState = STATE_SMALL;
}
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ */
public void animateOpen() {
- if (!(getParent() instanceof DragLayer)) return;
+ Folder openFolder = getOpen(mLauncher);
+ if (openFolder != null && openFolder != this) {
+ // Close any open folder before opening a folder.
+ openFolder.close(true);
+ }
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ // 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 (getParent() == null) {
+ dragLayer.addView(this);
+ mDragController.addDropTarget(this);
+ } else {
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
+ + getParent());
+ }
+ }
+
+ mIsOpen = true;
+ mFolderIcon.growAndFadeOut();
mContent.completePendingPageChanges();
if (!mDragInProgress) {
@@ -542,83 +549,63 @@
// dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
mDeleteFolderOnDropCompleted = false;
- Animator openFolderAnim = null;
final Runnable onCompleteRunnable;
- if (!Utilities.ATLEAST_LOLLIPOP) {
- positionAndSizeAsIcon();
- centerAboutIcon();
+ prepareReveal();
+ centerAboutIcon();
- final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1);
- oa.setDuration(mExpandDuration);
- openFolderAnim = oa;
+ AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+ int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ int height = getFolderHeight();
- setLayerType(LAYER_TYPE_HARDWARE, null);
- onCompleteRunnable = new Runnable() {
- @Override
- public void run() {
- setLayerType(LAYER_TYPE_NONE, null);
- }
- };
- } else {
- prepareReveal();
- centerAboutIcon();
+ float transX = - 0.075f * (width / 2 - getPivotX());
+ float transY = - 0.075f * (height / 2 - getPivotY());
+ setTranslationX(transX);
+ setTranslationY(transY);
+ PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
+ PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
- AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
- int height = getFolderHeight();
+ Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
+ drift.setDuration(mMaterialExpandDuration);
+ drift.setStartDelay(mMaterialExpandStagger);
+ drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
- float transX = - 0.075f * (width / 2 - getPivotX());
- float transY = - 0.075f * (height / 2 - getPivotY());
- setTranslationX(transX);
- setTranslationY(transY);
- PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
- PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
+ int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
+ int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
+ float radius = (float) Math.hypot(rx, ry);
- Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
- drift.setDuration(mMaterialExpandDuration);
- drift.setStartDelay(mMaterialExpandStagger);
- drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+ Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
+ (int) getPivotY(), 0, radius).createRevealAnimator(this);
+ reveal.setDuration(mMaterialExpandDuration);
+ reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
- int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
- int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
- float radius = (float) Math.hypot(rx, ry);
+ mContent.setAlpha(0f);
+ Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
+ iconsAlpha.setDuration(mMaterialExpandDuration);
+ iconsAlpha.setStartDelay(mMaterialExpandStagger);
+ iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
- Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
- (int) getPivotY(), 0, radius).createRevealAnimator(this);
- reveal.setDuration(mMaterialExpandDuration);
- reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+ mFooter.setAlpha(0f);
+ Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
+ textAlpha.setDuration(mMaterialExpandDuration);
+ textAlpha.setStartDelay(mMaterialExpandStagger);
+ textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
- mContent.setAlpha(0f);
- Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
- iconsAlpha.setDuration(mMaterialExpandDuration);
- iconsAlpha.setStartDelay(mMaterialExpandStagger);
- iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+ anim.play(drift);
+ anim.play(iconsAlpha);
+ anim.play(textAlpha);
+ anim.play(reveal);
- mFooter.setAlpha(0f);
- Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
- textAlpha.setDuration(mMaterialExpandDuration);
- textAlpha.setStartDelay(mMaterialExpandStagger);
- textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
- anim.play(drift);
- anim.play(iconsAlpha);
- anim.play(textAlpha);
- anim.play(reveal);
-
- openFolderAnim = anim;
-
- mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
- mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
- onCompleteRunnable = new Runnable() {
- @Override
- public void run() {
- mContent.setLayerType(LAYER_TYPE_NONE, null);
- mFooter.setLayerType(LAYER_TYPE_NONE, null);
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
- }
- };
- }
- openFolderAnim.addListener(new AnimatorListenerAdapter() {
+ mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
+ onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mContent.setLayerType(LAYER_TYPE_NONE, null);
+ mFooter.setLayerType(LAYER_TYPE_NONE, null);
+ mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+ }
+ };
+ anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
Utilities.sendCustomAccessibilityEvent(
@@ -649,7 +636,7 @@
// Do not update the flag if we are in drag mode. The flag will be updated, when we
// actually drop the icon.
final boolean updateAnimationFlag = !mDragInProgress;
- openFolderAnim.addListener(new AnimatorListenerAdapter() {
+ anim.addListener(new AnimatorListenerAdapter() {
@SuppressLint("InlinedApi")
@Override
@@ -672,7 +659,7 @@
}
mPageIndicator.stopAllAnimations();
- openFolderAnim.start();
+ anim.start();
// Make sure the folder picks up the last drag move even if the finger doesn't move.
if (mDragController.isDragging()) {
@@ -680,6 +667,11 @@
}
mContent.verifyVisibleHighResIcons(mContent.getNextPage());
+
+ // Notify the accessibility manager that this folder "window" has appeared and occluded
+ // the workspace items
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ dragLayer.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
public void beginExternalDrag() {
@@ -692,14 +684,44 @@
mDragController.addDragListener(this);
}
- public void animateClosed() {
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_FOLDER) != 0;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mIsOpen = false;
+
+ if (isEditingName()) {
+ mFolderName.dispatchBackKey();
+ }
+
+ if (mFolderIcon != null) {
+ mFolderIcon.shrinkAndFadeIn(animate);
+ }
+
if (!(getParent() instanceof DragLayer)) return;
+ DragLayer parent = (DragLayer) getParent();
+
+ if (animate) {
+ animateClosed();
+ } else {
+ closeComplete(false);
+ }
+
+ // Notify the accessibility manager that this folder "window" has disappeared and no
+ // longer occludes the workspace items
+ parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ private void animateClosed() {
final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
oa.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setLayerType(LAYER_TYPE_NONE, null);
- close(true);
+ closeComplete(true);
}
@Override
public void onAnimationStart(Animator animation) {
@@ -715,7 +737,7 @@
oa.start();
}
- public void close(boolean wasAnimated) {
+ private void closeComplete(boolean wasAnimated) {
// TODO: Clear all active animations.
DragLayer parent = (DragLayer) getParent();
if (parent != null) {
@@ -850,8 +872,8 @@
};
public void completeDragExit() {
- if (mInfo.opened) {
- mLauncher.closeFolder();
+ if (mIsOpen) {
+ close(true);
mRearrangeOnClose = true;
} else if (mState == STATE_ANIMATING) {
mRearrangeOnClose = true;
@@ -916,7 +938,7 @@
if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
replaceFolderWithFinalItem();
}
- } else if (!mDragController.isDeferringDrag()) {
+ } else {
// The drag failed, we need to return the item to the folder
ShortcutInfo info = (ShortcutInfo) d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
@@ -1309,9 +1331,7 @@
mIsExternalDrag = false;
} else {
currentDragView = mCurrentDragView;
- if (!mDragController.isDeferringDrag()) {
- mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
- }
+ mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
}
if (d.dragView.hasDrawn()) {
@@ -1332,11 +1352,9 @@
mItemsInvalidated = true;
rearrangeChildren();
- if (!mDragController.isDeferringDrag()) {
- // Temporarily suppress the listener, as we did all the work already here.
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.add(si, false);
- }
+ // Temporarily suppress the listener, as we did all the work already here.
+ try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+ mInfo.add(si, false);
}
// Clear the drag info, as it is no longer being dragged.
@@ -1382,8 +1400,8 @@
rearrangeChildren();
}
if (getItemCount() <= 1) {
- if (mInfo.opened) {
- mLauncher.closeFolder(this, true);
+ if (mIsOpen) {
+ close(true);
} else {
replaceFolderWithFinalItem();
}
@@ -1429,7 +1447,7 @@
if (hasFocus) {
startEditingFolderName();
} else {
- dismissEditingName();
+ mFolderName.dispatchBackKey();
}
}
}
@@ -1442,7 +1460,7 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.gridX = info.cellX;
target.gridY = info.cellY;
target.pageIndex = mContent.getCurrentPage();
@@ -1528,4 +1546,11 @@
updateTextViewFocus();
}
}
+
+ /**
+ * Returns a folder which is already open or null
+ */
+ public static Folder getOpen(Launcher launcher) {
+ return getOpenView(launcher, TYPE_FOLDER);
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 69c2b0f..a29a946 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
@@ -56,7 +57,6 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.PreloadIconDrawable;
@@ -142,6 +142,7 @@
mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
new StackFolderIconLayoutRule() :
new ClippedFolderIconLayoutRule();
+ mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
@@ -202,16 +203,12 @@
updateItemDrawingParams(false);
}
- public FolderInfo getFolderInfo() {
- return mInfo;
- }
-
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
- !mFolder.isFull() && item != mInfo && !mInfo.opened);
+ !mFolder.isFull() && item != mInfo && !mFolder.isOpen());
}
public boolean acceptDrop(ItemInfo dragInfo) {
@@ -243,7 +240,7 @@
OnAlarmListener mOnOpenListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) {
mFolder.beginExternalDrag();
- mLauncher.openFolder(FolderIcon.this);
+ mFolder.animateOpen();
}
};
@@ -974,12 +971,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- @Override
public void cancelLongPress() {
super.cancelLongPress();
mLongPressHelper.cancelLongPress();
@@ -990,13 +981,76 @@
mInfo.removeListener(mFolder);
}
+ public void shrinkAndFadeIn(boolean animate) {
+ final CellLayout cl = (CellLayout) getParent().getParent();
+ ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+
+ // We remove and re-draw the FolderIcon in-case it has changed
+ final PreviewImageView previewImage = PreviewImageView.get(getContext());
+ previewImage.removeFromParent();
+ copyToPreview(previewImage);
+
+ if (cl != null) {
+ cl.clearFolderLeaveBehind();
+ }
+
+ ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1);
+ oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
+ oa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (cl != null) {
+ // Remove the ImageView copy of the FolderIcon and make the original visible.
+ previewImage.removeFromParent();
+ setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ oa.start();
+ if (!animate) {
+ oa.end();
+ }
+ }
+
+ public void growAndFadeOut() {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+ // While the folder is open, the position of the icon cannot change.
+ lp.canReorder = false;
+ if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ CellLayout cl = (CellLayout) getParent().getParent();
+ cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ }
+
+ // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
+ PreviewImageView previewImage = PreviewImageView.get(getContext());
+ copyToPreview(previewImage);
+ setVisibility(View.INVISIBLE);
+
+ ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 0, 1.5f, 1.5f);
+ oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
+ oa.start();
+ }
+
+ /**
+ * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
+ * in the DragLayer in the exact absolute location of the original FolderIcon.
+ */
+ private void copyToPreview(PreviewImageView previewImageView) {
+ previewImageView.copy(this);
+ if (mFolder != null) {
+ previewImageView.setPivotX(mFolder.getPivotXForIconAnimation());
+ previewImageView.setPivotY(mFolder.getPivotYForIconAnimation());
+ mFolder.bringToFront();
+ }
+ }
+
public interface PreviewLayoutRule {
- public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
+ PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
PreviewItemDrawingParams params);
- public void init(int availableSpace, int intrinsicIconSize, boolean rtl);
+ void init(int availableSpace, int intrinsicIconSize, boolean rtl);
- public int numItems();
- public boolean clipToBackground();
+ int numItems();
+ boolean clipToBackground();
}
}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 7e7ee34..e71c5e9 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -256,6 +256,7 @@
CellLayout page = new CellLayout(getContext());
page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+ page.getShortcutsAndWidgets().setContainerType(ShortcutAndWidgetContainer.FOLDER);
page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
page.setInvertIfRtl(true);
page.setGridSize(mGridCountX, mGridCountY);
@@ -524,12 +525,11 @@
}
@Override
- protected void onPageBeginMoving() {
- super.onPageBeginMoving();
- getVisiblePages(sTempPosArray);
- for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
- verifyVisibleHighResIcons(i);
- }
+ protected void onPageBeginTransition() {
+ super.onPageBeginTransition();
+ // Ensure that adjacent pages have high resolution icons
+ verifyVisibleHighResIcons(getCurrentPage() - 1);
+ verifyVisibleHighResIcons(getCurrentPage() + 1);
}
/**
@@ -679,7 +679,7 @@
}
@Override
- protected void getEdgeVerticalPostion(int[] pos) {
+ protected void getEdgeVerticalPosition(int[] pos) {
pos[0] = 0;
pos[1] = getViewportHeight();
}
diff --git a/src/com/android/launcher3/folder/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java
new file mode 100644
index 0000000..c4f3ee1
--- /dev/null
+++ b/src/com/android/launcher3/folder/PreviewImageView.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.folder;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragLayer;
+
+/**
+ * A temporary view which displays the a bitmap (used for folder icon animation)
+ */
+public class PreviewImageView extends ImageView {
+
+ private final Rect mTempRect = new Rect();
+ private final DragLayer mParent;
+
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+
+ public PreviewImageView(DragLayer parent) {
+ super(parent.getContext());
+ mParent = parent;
+ }
+
+ public void copy(View view) {
+ final int width = view.getMeasuredWidth();
+ final int height = view.getMeasuredHeight();
+
+ if (mBitmap == null || mBitmap.getWidth() != width || mBitmap.getHeight() != height) {
+ mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ }
+
+ DragLayer.LayoutParams lp;
+ if (getLayoutParams() instanceof DragLayer.LayoutParams) {
+ lp = (DragLayer.LayoutParams) getLayoutParams();
+ } else {
+ lp = new DragLayer.LayoutParams(width, height);
+ }
+
+ // The layout from which the folder is being opened may be scaled, adjust the starting
+ // view size by this scale factor.
+ float scale = mParent.getDescendantRectRelativeToSelf(view, mTempRect);
+ lp.customPosition = true;
+ lp.x = mTempRect.left;
+ lp.y = mTempRect.top;
+ lp.width = (int) (scale * width);
+ lp.height = (int) (scale * height);
+
+ mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ view.draw(mCanvas);
+ setImageBitmap(mBitmap);
+
+ // Just in case this image view is still in the drag layer from a previous animation,
+ // we remove it and re-add it.
+ removeFromParent();
+ mParent.addView(this, lp);
+ }
+
+ public void removeFromParent() {
+ if (mParent.indexOfChild(this) != -1) {
+ mParent.removeView(this);
+ }
+ }
+
+ public static PreviewImageView get(Context context) {
+ DragLayer dragLayer = Launcher.getLauncher(context).getDragLayer();
+ PreviewImageView view = (PreviewImageView) dragLayer.getTag(R.id.preview_image_id);
+ if (view == null) {
+ view = new PreviewImageView(dragLayer);
+ dragLayer.setTag(R.id.preview_image_id, view);
+ }
+ return view;
+ }
+}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bc91c15..a7d4c63 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,7 +24,6 @@
import android.view.View;
import android.widget.TextView;
-import com.android.launcher3.HolographicOutlineHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.PreloadIconDrawable;
import com.android.launcher3.Workspace;
@@ -45,7 +44,7 @@
// The padding added to the drag view during the preview generation.
public final int previewPadding;
- public Bitmap gerenatedDragOutline;
+ public Bitmap generatedDragOutline;
public DragPreviewProvider(View view) {
mView = view;
@@ -121,11 +120,11 @@
}
public final void generateDragOutline(Canvas canvas) {
- if (ProviderConfig.IS_DOGFOOD_BUILD && gerenatedDragOutline != null) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD && generatedDragOutline != null) {
throw new RuntimeException("Drag outline generated twice");
}
- gerenatedDragOutline = createDragOutline(canvas);
+ generatedDragOutline = createDragOutline(canvas);
}
/**
@@ -137,7 +136,7 @@
mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
canvas.setBitmap(b);
drawDragView(canvas);
- HolographicOutlineHelper.obtain(mView.getContext())
+ HolographicOutlineHelper.getInstance(mView.getContext())
.applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
similarity index 90%
rename from src/com/android/launcher3/HolographicOutlineHelper.java
rename to src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9dec7d9..9c39721 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.graphics;
import android.content.Context;
import android.content.res.Resources;
@@ -29,6 +29,8 @@
import android.graphics.drawable.Drawable;
import android.util.SparseArray;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
import com.android.launcher3.config.ProviderConfig;
import java.nio.ByteBuffer;
@@ -72,9 +74,9 @@
mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
- public static HolographicOutlineHelper obtain(Context context) {
+ public static HolographicOutlineHelper getInstance(Context context) {
if (sInstance == null) {
- sInstance = new HolographicOutlineHelper(context);
+ sInstance = new HolographicOutlineHelper(context.getApplicationContext());
}
return sInstance;
}
@@ -155,19 +157,14 @@
thickInnerBlur.recycle();
}
- Bitmap createMediumDropShadow(BubbleTextView view) {
- return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
- }
-
- Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
- return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
- }
-
- Bitmap createMediumDropShadow(Drawable drawable, float scaleX, float scaleY,
- boolean shouldCache) {
+ public Bitmap createMediumDropShadow(BubbleTextView view) {
+ Drawable drawable = view.getIcon();
if (drawable == null) {
return null;
}
+
+ float scaleX = view.getScaleX();
+ float scaleY = view.getScaleY();
Rect rect = drawable.getBounds();
int bitmapWidth = (int) (rect.width() * scaleX);
@@ -177,14 +174,11 @@
}
int key = (bitmapWidth << 16) | bitmapHeight;
- Bitmap cache = shouldCache ? mBitmapCache.get(key) : null;
+ Bitmap cache = mBitmapCache.get(key);
if (cache == null) {
cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
mCanvas.setBitmap(cache);
-
- if (shouldCache) {
- mBitmapCache.put(key, cache);
- }
+ mBitmapCache.put(key, cache);
} else {
mCanvas.setBitmap(cache);
mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
@@ -204,7 +198,7 @@
int resultWidth = bitmapWidth + extraSize;
int resultHeight = bitmapHeight + extraSize;
key = (resultWidth << 16) | resultHeight;
- Bitmap result = shouldCache ? mBitmapCache.get(key) : null;
+ Bitmap result = mBitmapCache.get(key);
if (result == null) {
result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
mCanvas.setBitmap(result);
diff --git a/src/com/android/launcher3/util/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
similarity index 92%
rename from src/com/android/launcher3/util/IconNormalizer.java
rename to src/com/android/launcher3/graphics/IconNormalizer.java
index 040a1b5..1410917 100644
--- a/src/com/android/launcher3/util/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.launcher3.graphics;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -186,16 +186,16 @@
}
/**
- * Modifies {@param xCordinates} to represent a convex border. Fills in all missing values
+ * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
* (except on either ends) with appropriate values.
- * @param xCordinates map of x coordinate per y.
+ * @param xCoordinates map of x coordinate per y.
* @param direction 1 for left border and -1 for right border.
* @param topY the first Y position (inclusive) with a valid value.
* @param bottomY the last Y position (inclusive) with a valid value.
*/
private static void convertToConvexArray(
- float[] xCordinates, int direction, int topY, int bottomY) {
- int total = xCordinates.length;
+ float[] xCoordinates, int direction, int topY, int bottomY) {
+ int total = xCoordinates.length;
// The tangent at each pixel.
float[] angles = new float[total - 1];
@@ -205,7 +205,7 @@
float lastAngle = Float.MAX_VALUE;
for (int i = topY + 1; i <= bottomY; i++) {
- if (xCordinates[i] <= -1) {
+ if (xCoordinates[i] <= -1) {
continue;
}
int start;
@@ -213,14 +213,14 @@
if (lastAngle == Float.MAX_VALUE) {
start = first;
} else {
- float currentAngle = (xCordinates[i] - xCordinates[last]) / (i - last);
+ float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
start = last;
// If this position creates a concave angle, keep moving up until we find a
// position which creates a convex angle.
if ((currentAngle - lastAngle) * direction < 0) {
while (start > first) {
start --;
- currentAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+ currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
if ((currentAngle - angles[start]) * direction >= 0) {
break;
}
@@ -229,11 +229,11 @@
}
// Reset from last check
- lastAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+ lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
// Update all the points from start.
for (int j = start; j < i; j++) {
angles[j] = lastAngle;
- xCordinates[j] = xCordinates[start] + lastAngle * (j - start);
+ xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
}
last = i;
}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
new file mode 100644
index 0000000..9f3f357
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.graphics;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Helper methods for generating various launcher icons
+ */
+public class LauncherIcons {
+
+ private static final Rect sOldBounds = new Rect();
+ private static final Canvas sCanvas = new Canvas();
+
+ static {
+ sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+
+
+ public 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.
+ */
+ public static Bitmap createIconBitmap(String packageName, String resourceName,
+ Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ // the resource
+ try {
+ Resources resources = packageManager.getResourcesForApplication(packageName);
+ if (resources != null) {
+ final int id = resources.getIdentifier(resourceName, null, null);
+ return createIconBitmap(
+ resources.getDrawableForDensity(id, LauncherAppState.getInstance()
+ .getInvariantDeviceProfile().fillResIconDpi), context);
+ }
+ } catch (Exception e) {
+ // Icon not found.
+ }
+ return null;
+ }
+
+ private static int getIconBitmapSize() {
+ return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
+ }
+
+ /**
+ * Returns a bitmap which is of the appropriate size to be displayed as an icon
+ */
+ public static Bitmap createIconBitmap(Bitmap icon, Context context) {
+ final int iconBitmapSize = getIconBitmapSize();
+ if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+ return icon;
+ }
+ return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+ * The bitmap is also visually normalized with other icons.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bitmap createBadgedIconBitmap(
+ Drawable icon, UserHandleCompat user, Context context) {
+ float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+ 1 : IconNormalizer.getInstance().getScale(icon, null);
+ Bitmap bitmap = createIconBitmap(icon, context, scale);
+ return badgeIconForUser(bitmap, user, context);
+ }
+
+ /**
+ * Badges the provided icon with the user badge if required.
+ */
+ public static Bitmap badgeIconForUser(Bitmap icon, UserHandleCompat user, Context context) {
+ if (Utilities.ATLEAST_LOLLIPOP && user != null
+ && !UserHandleCompat.myUserHandle().equals(user)) {
+ BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
+ Drawable badged = context.getPackageManager().getUserBadgedIcon(
+ drawable, user.getUser());
+ if (badged instanceof BitmapDrawable) {
+ return ((BitmapDrawable) badged).getBitmap();
+ } else {
+ return createIconBitmap(badged, context);
+ }
+ } else {
+ return icon;
+ }
+ }
+
+ /**
+ * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
+ * normalized with other icons and has enough spacing to add shadow.
+ */
+ public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
+ RectF iconBounds = new RectF();
+ float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+ 1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
+ scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
+ return createIconBitmap(icon, context, scale);
+ }
+
+ /**
+ * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
+ * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
+ */
+ public static Bitmap addShadowToIcon(Bitmap icon) {
+ return ShadowGenerator.getInstance().recreateIcon(icon);
+ }
+
+ /**
+ * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
+ int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+ synchronized (sCanvas) {
+ sCanvas.setBitmap(srcTgt);
+ sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
+ new Rect(srcTgt.getWidth() - badgeSize,
+ srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
+ new Paint(Paint.FILTER_BITMAP_FLAG));
+ sCanvas.setBitmap(null);
+ }
+ return srcTgt;
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view.
+ */
+ public static Bitmap createIconBitmap(Drawable icon, Context context) {
+ return createIconBitmap(icon, context, 1.0f /* scale */);
+ }
+
+ /**
+ * @param scale the scale to apply before drawing {@param icon} on the canvas
+ */
+ public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
+ synchronized (sCanvas) {
+ final int iconBitmapSize = getIconBitmapSize();
+
+ int width = iconBitmapSize;
+ int height = iconBitmapSize;
+
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
+ }
+ }
+ int sourceWidth = icon.getIntrinsicWidth();
+ int sourceHeight = icon.getIntrinsicHeight();
+ if (sourceWidth > 0 && sourceHeight > 0) {
+ // Scale the icon proportionally to the icon dimensions
+ final float ratio = (float) sourceWidth / sourceHeight;
+ if (sourceWidth > sourceHeight) {
+ height = (int) (width / ratio);
+ } else if (sourceHeight > sourceWidth) {
+ width = (int) (height * ratio);
+ }
+ }
+
+ // no intrinsic size --> use default size
+ int textureWidth = iconBitmapSize;
+ int textureHeight = iconBitmapSize;
+
+ final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = sCanvas;
+ canvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ sOldBounds.set(icon.getBounds());
+ icon.setBounds(left, top, left+width, top+height);
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+ icon.draw(canvas);
+ canvas.restore();
+ icon.setBounds(sOldBounds);
+ canvas.setBitmap(null);
+
+ return bitmap;
+ }
+ }
+
+ /**
+ * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+ * This allows the badging to be done based on the action bitmap size rather than
+ * the scaled bitmap size.
+ */
+ private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+ public FixedSizeBitmapDrawable(Bitmap bitmap) {
+ super(null, bitmap);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return getBitmap().getWidth();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return getBitmap().getWidth();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
new file mode 100644
index 0000000..6603e93
--- /dev/null
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyboard;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Handles showing a popup menu with available custom actions for a launcher icon.
+ * This allows exposing various custom actions using keyboard shortcuts.
+ */
+public class CustomActionsPopup implements OnMenuItemClickListener {
+
+ private final Launcher mLauncher;
+ private final LauncherAccessibilityDelegate mDelegate;
+ private final View mIcon;
+
+ public CustomActionsPopup(Launcher launcher, View icon) {
+ mLauncher = launcher;
+ mIcon = icon;
+ DeepShortcutsContainer container = DeepShortcutsContainer.getOpen(launcher);
+ if (container != null) {
+ mDelegate = container.getAccessibilityDelegate();
+ } else {
+ mDelegate = launcher.getAccessibilityDelegate();
+ }
+ }
+
+ private List<AccessibilityAction> getActionList() {
+ if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
+ return Collections.EMPTY_LIST;
+ }
+
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mDelegate.addSupportedActions(mIcon, info, true);
+ List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
+ info.recycle();
+ return result;
+ }
+
+ public boolean canShow() {
+ return !getActionList().isEmpty();
+ }
+
+ public boolean show() {
+ List<AccessibilityAction> actions = getActionList();
+ if (actions.isEmpty()) {
+ return false;
+ }
+
+ PopupMenu popup = new PopupMenu(mLauncher, mIcon);
+ popup.setOnMenuItemClickListener(this);
+ Menu menu = popup.getMenu();
+ for (AccessibilityAction action : actions) {
+ menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
+ }
+ popup.show();
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId());
+ }
+}
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 7672f5a..b0d6b2d 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -143,7 +143,7 @@
}
private Rect getDrawRect() {
- if (mCurrentView != null) {
+ if (mCurrentView != null && mCurrentView.isAttachedToWindow()) {
viewToRect(mCurrentView, sTempRect1);
if (mShift > 0 && mTargetView != null) {
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 53a2f5d..abe8f42 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -38,7 +38,7 @@
/**
* Manages the creation of {@link LauncherEvent}.
- * To debug this class, execute following command before sideloading a new apk.
+ * To debug this class, execute following command before side loading a new apk.
*
* $ adb shell setprop log.tag.UserEvent VERBOSE
*/
@@ -49,13 +49,9 @@
private final boolean mIsVerbose;
/**
- * TODO: change the name of this interface to LogContainerProvider
- * and the method name to fillInLogContainerData. Not changed to minimize CL diff
- * in this branch.
- *
- * Implemented by containers to provide a launch source for a given child.
+ * Implemented by containers to provide a container source for a given child.
*/
- public interface LaunchSourceProvider {
+ public interface LogContainerProvider {
/**
* Copies data from the source to the destination proto.
@@ -65,13 +61,13 @@
* @param target dest of the data
* @param targetParent dest of the data
*/
- void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent);
+ void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
}
/**
* Recursively finds the parent of the given child which implements IconLogInfoProvider
*/
- public static LaunchSourceProvider getLaunchProviderRecursive(View v) {
+ public static LogContainerProvider getLaunchProviderRecursive(View v) {
ViewParent parent = null;
if (v != null) {
@@ -83,8 +79,8 @@
// Optimization to only check up to 5 parents.
int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
while (parent != null && count-- > 0) {
- if (parent instanceof LaunchSourceProvider) {
- return (LaunchSourceProvider) parent;
+ if (parent instanceof LogContainerProvider) {
+ return (LogContainerProvider) parent;
} else {
parent = parent.getParent();
}
@@ -124,12 +120,12 @@
// Fill in grid(x,y), pageIndex of the child and container type of the parent
// TODO: make this percolate up the view hierarchy if needed.
int idx = 0;
- LaunchSourceProvider provider = getLaunchProviderRecursive(v);
+ LogContainerProvider provider = getLaunchProviderRecursive(v);
if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
return null;
}
ItemInfo itemInfo = (ItemInfo) v.getTag();
- provider.fillInLaunchSourceData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
+ provider.fillInLogContainerData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
event.srcTarget[idx].intentHash = intent.hashCode();
ComponentName cn = intent.getComponent();
@@ -167,24 +163,31 @@
}
public void logActionOnContainer(int action, int dir, int containerType) {
+ logActionOnContainer(action, dir, containerType, 0);
+ }
+
+ public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTAINER);
event.action.touch = action;
event.action.dir = dir;
event.srcTarget[0].containerType = containerType;
+ event.srcTarget[0].pageIndex = pageIndex;
dispatchUserEvent(event, null);
}
public void logDeepShortcutsOpen(View icon) {
LauncherEvent event = LoggerUtils.initLauncherEvent(
Action.TOUCH, icon, Target.CONTAINER);
- LaunchSourceProvider provider = getLaunchProviderRecursive(icon);
+ LogContainerProvider provider = getLaunchProviderRecursive(icon);
if (icon == null && !(icon.getTag() instanceof ItemInfo)) {
return;
}
ItemInfo info = (ItemInfo) icon.getTag();
- provider.fillInLaunchSourceData(icon, info, event.srcTarget[0], event.srcTarget[1]);
+ provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
event.action.touch = Action.LONGPRESS;
dispatchUserEvent(event, null);
+
+ resetElapsedContainerMillis();
}
public void setPredictedApps(List<ComponentKey> predictedApps) {
@@ -218,11 +221,11 @@
dropTargetAsView);
event.action.touch = Action.DRAGDROP;
- dragObj.dragSource.fillInLaunchSourceData(null, dragObj.originalDragInfo,
+ dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
event.srcTarget[0], event.srcTarget[1]);
- if (dropTargetAsView instanceof LaunchSourceProvider) {
- ((LaunchSourceProvider) dropTargetAsView).fillInLaunchSourceData(null,
+ if (dropTargetAsView instanceof LogContainerProvider) {
+ ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
new file mode 100644
index 0000000..986e163
--- /dev/null
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.LongSparseArray;
+import android.util.Pair;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.GridOccupancy;
+
+import java.util.ArrayList;
+
+/**
+ * Task to add auto-created workspace items.
+ */
+public class AddWorkspaceItemsTask extends ExtendedModelTask {
+
+ private final ArrayList<? extends ItemInfo> mWorkspaceApps;
+
+ /**
+ * @param workspaceApps items to add on the workspace
+ */
+ public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) {
+ mWorkspaceApps = workspaceApps;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ if (mWorkspaceApps.isEmpty()) {
+ return;
+ }
+ Context context = app.getContext();
+
+ final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
+ final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+
+ // 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 = LauncherModel.loadWorkspaceScreensDb(context);
+ synchronized(dataModel) {
+ for (ItemInfo item : mWorkspaceApps) {
+ if (item instanceof ShortcutInfo) {
+ // Short-circuit this logic if the icon exists somewhere on the workspace
+ if (shortcutExists(dataModel, item.getIntent(), item.user)) {
+ continue;
+ }
+ }
+
+ // Find appropriate space for the item.
+ Pair<Long, int[]> coords = findSpaceForItem(
+ app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
+ long screenId = coords.first;
+ int[] cordinates = coords.second;
+
+ ItemInfo itemInfo;
+ if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+ itemInfo = item;
+ } else if (item instanceof AppInfo) {
+ itemInfo = ((AppInfo) item).makeShortcut();
+ } else {
+ throw new RuntimeException("Unexpected info type");
+ }
+
+ // Add the shortcut to the db
+ addItemToDatabase(context, itemInfo, screenId, cordinates);
+
+ // Save the ShortcutInfo for binding in the workspace
+ addedShortcutsFinal.add(itemInfo);
+ }
+ }
+
+ // Update the workspace screens
+ updateScreens(context, workspaceScreens);
+
+ if (!addedShortcutsFinal.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+ final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+ if (!addedShortcutsFinal.isEmpty()) {
+ ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+ long lastScreenId = info.screenId;
+ for (ItemInfo i : addedShortcutsFinal) {
+ if (i.screenId == lastScreenId) {
+ addAnimated.add(i);
+ } else {
+ addNotAnimated.add(i);
+ }
+ }
+ }
+ callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+ addNotAnimated, addAnimated, null);
+ }
+ });
+ }
+ }
+
+ protected void addItemToDatabase(Context context, ItemInfo item, long screenId, int[] pos) {
+ LauncherModel.addItemToDatabase(context, item,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, pos[0], pos[1]);
+ }
+
+ protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
+ LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
+ }
+
+ /**
+ * Returns true if the shortcuts already exists on the workspace. This must be called after
+ * the workspace has been loaded. We identify a shortcut by its intent.
+ */
+ protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandleCompat user) {
+ final String intentWithPkg, intentWithoutPkg;
+ if (intent.getComponent() != null) {
+ // If component is not null, an intent with null package will produce
+ // the same result and should also be a match.
+ String packageName = intent.getComponent().getPackageName();
+ if (intent.getPackage() != null) {
+ intentWithPkg = intent.toUri(0);
+ intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
+ } else {
+ intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
+ intentWithoutPkg = intent.toUri(0);
+ }
+ } else {
+ intentWithPkg = intent.toUri(0);
+ intentWithoutPkg = intent.toUri(0);
+ }
+
+ synchronized (dataModel) {
+ for (ItemInfo item : dataModel.itemsIdMap) {
+ if (item instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) item;
+ Intent targetIntent = info.promisedIntent == null
+ ? info.intent : info.promisedIntent;
+ if (targetIntent != null && info.user.equals(user)) {
+ Intent copyIntent = new Intent(targetIntent);
+ copyIntent.setSourceBounds(intent.getSourceBounds());
+ String s = copyIntent.toUri(0);
+ if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find a position on the screen for the given size or adds a new screen.
+ * @return screenId and the coordinates for the item.
+ */
+ protected Pair<Long, int[]> findSpaceForItem(
+ LauncherAppState app, BgDataModel dataModel,
+ ArrayList<Long> workspaceScreens,
+ ArrayList<Long> addedWorkspaceScreensFinal,
+ int spanX, int spanY) {
+ LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
+
+ // Use sBgItemsIdMap as all the items are already loaded.
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ ArrayList<ItemInfo> items = screenItems.get(info.screenId);
+ if (items == null) {
+ items = new ArrayList<>();
+ screenItems.put(info.screenId, items);
+ }
+ items.add(info);
+ }
+ }
+ }
+
+ // Find appropriate space for the item.
+ long screenId = 0;
+ int[] cordinates = new int[2];
+ boolean found = false;
+
+ int screenCount = workspaceScreens.size();
+ // First check the preferred screen.
+ int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
+ if (preferredScreenIndex < screenCount) {
+ screenId = workspaceScreens.get(preferredScreenIndex);
+ found = findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), cordinates, spanX, spanY);
+ }
+
+ if (!found) {
+ // Search on any of the screens starting from the first screen.
+ for (int screen = 1; screen < screenCount; screen++) {
+ screenId = workspaceScreens.get(screen);
+ if (findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+ // We found a space for it
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ // Still no position found. Add a new screen to the end.
+ screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+
+ // Save the screen id for binding in the workspace
+ workspaceScreens.add(screenId);
+ addedWorkspaceScreensFinal.add(screenId);
+
+ // If we still can't find an empty space, then God help us all!!!
+ if (!findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+ throw new RuntimeException("Can't find space to add the item");
+ }
+ }
+ return Pair.create(screenId, cordinates);
+ }
+
+ private boolean findNextAvailableIconSpaceInScreen(
+ LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
+ int[] xy, int spanX, int spanY) {
+ InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+ GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
+ if (occupiedPos != null) {
+ for (ItemInfo r : occupiedPos) {
+ occupied.markCells(r, true);
+ }
+ }
+ return occupied.findVacantCell(xy, spanX, spanY);
+ }
+
+}
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
deleted file mode 100644
index 5f80037..0000000
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.model;
-
-import android.content.Context;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.util.Thunk;
-
-import java.text.Collator;
-import java.util.Comparator;
-
-/**
- * Class to manage access to an app name comparator.
- * <p>
- * Used to sort application name in all apps view and widget tray view.
- */
-public class AppNameComparator {
- private final Collator mCollator;
- private final AbstractUserComparator<ItemInfo> mAppInfoComparator;
- private final Comparator<String> mSectionNameComparator;
-
- public AppNameComparator(Context context) {
- mCollator = Collator.getInstance();
- mAppInfoComparator = new AbstractUserComparator<ItemInfo>(context) {
-
- @Override
- public final int compare(ItemInfo a, ItemInfo b) {
- // Order by the title in the current locale
- int result = compareTitles(a.title.toString(), b.title.toString());
- if (result == 0 && a instanceof AppInfo && b instanceof AppInfo) {
- AppInfo aAppInfo = (AppInfo) a;
- AppInfo bAppInfo = (AppInfo) b;
- // If two apps have the same title, then order by the component name
- result = aAppInfo.componentName.compareTo(bAppInfo.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)
- return super.compare(a, b);
- }
- }
- return result;
- }
- };
- mSectionNameComparator = new Comparator<String>() {
- @Override
- public int compare(String o1, String o2) {
- return compareTitles(o1, o2);
- }
- };
- }
-
- /**
- * Returns a locale-aware comparator that will alphabetically order a list of applications.
- */
- public Comparator<ItemInfo> getAppInfoComparator() {
- return mAppInfoComparator;
- }
-
- /**
- * Returns a locale-aware comparator that will alphabetically order a list of section names.
- */
- public Comparator<String> getSectionNameComparator() {
- return mSectionNameComparator;
- }
-
- /**
- * Compares two titles with the same return value semantics as Comparator.
- */
- @Thunk int compareTitles(String titleA, String titleB) {
- // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
- boolean aStartsWithLetter = (titleA.length() > 0) &&
- Character.isLetterOrDigit(titleA.codePointAt(0));
- boolean bStartsWithLetter = (titleB.length() > 0) &&
- Character.isLetterOrDigit(titleB.codePointAt(0));
- if (aStartsWithLetter && !bStartsWithLetter) {
- return -1;
- } else if (!aStartsWithLetter && bStartsWithLetter) {
- return 1;
- }
-
- // Order by the title in the current locale
- return mCollator.compare(titleA, titleB);
- }
-}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
new file mode 100644
index 0000000..29defdd
--- /dev/null
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * All the data stored in-memory and managed by the LauncherModel
+ */
+public class BgDataModel {
+
+ private static final String TAG = "BgDataModel";
+
+ /**
+ * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
+ * LauncherModel to their ids
+ */
+ public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
+
+ /**
+ * List of all the folders and shortcuts directly on the home screen (no widgets
+ * or shortcuts within folders).
+ */
+ public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+
+ /**
+ * All LauncherAppWidgetInfo created by LauncherModel.
+ */
+ public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+
+ /**
+ * Map of id to FolderInfos of all the folders created by LauncherModel
+ */
+ public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
+
+ /**
+ * Ordered list of workspace screens ids.
+ */
+ public final ArrayList<Long> workspaceScreens = new ArrayList<>();
+
+ /**
+ * Map of ShortcutKey to the number of times it is pinned.
+ */
+ public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
+
+ /**
+ * Maps all launcher activities to the id's of their shortcuts (if they have any).
+ */
+ public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
+
+ /**
+ * Clears all the data
+ */
+ public synchronized void clear() {
+ workspaceItems.clear();
+ appWidgets.clear();
+ folders.clear();
+ itemsIdMap.clear();
+ workspaceScreens.clear();
+ pinnedShortcutCounts.clear();
+ deepShortcutMap.clear();
+ }
+
+ public synchronized void removeItem(ItemInfo... items) {
+ removeItem(Arrays.asList(items));
+ }
+
+ public synchronized void removeItem(Iterable<? extends ItemInfo> items) {
+ for (ItemInfo item : items) {
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ folders.remove(item.id);
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ for (ItemInfo info : itemsIdMap) {
+ if (info.container == item.id) {
+ // We are deleting a folder which still contains items that
+ // think they are contained by that folder.
+ String msg = "deleting a folder (" + item + ") which still " +
+ "contains items (" + info + ")";
+ Log.e(TAG, msg);
+ }
+ }
+ }
+ workspaceItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+ // Decrement pinned shortcut count
+ ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+ MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+ if (count == null || --count.value == 0) {
+ DeepShortcutManager.getInstance(LauncherAppState.getInstance().getContext())
+ .unpinShortcut(pinnedShortcut);
+ }
+ // Fall through.
+ }
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ workspaceItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ appWidgets.remove(item);
+ break;
+ }
+ itemsIdMap.remove(item.id);
+ }
+ }
+
+ public synchronized void addItem(ItemInfo item, boolean newItem) {
+ itemsIdMap.put(item.id, item);
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ folders.put(item.id, (FolderInfo) item);
+ workspaceItems.add(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+ // Increment the count for the given shortcut
+ ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+ MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+ if (count == null) {
+ count = new MutableInt(1);
+ pinnedShortcutCounts.put(pinnedShortcut, count);
+ } else {
+ count.value++;
+ }
+
+ // Since this is a new item, pin the shortcut in the system server.
+ if (newItem && count.value == 1) {
+ DeepShortcutManager.getInstance(LauncherAppState.getInstance().getContext())
+ .pinShortcut(pinnedShortcut);
+ }
+ // Fall through
+ }
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+ item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ workspaceItems.add(item);
+ } else {
+ if (newItem) {
+ if (!folders.containsKey(item.container)) {
+ // Adding an item to a folder that doesn't exist.
+ String msg = "adding item: " + item + " to a folder that " +
+ " doesn't exist";
+ Log.e(TAG, msg);
+ }
+ } else {
+ findOrMakeFolder(item.container).add((ShortcutInfo) item, false);
+ }
+
+ }
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ appWidgets.add((LauncherAppWidgetInfo) item);
+ break;
+ }
+ }
+
+ /**
+ * Return an existing FolderInfo object if we have encountered this ID previously,
+ * or make a new one.
+ */
+ public synchronized FolderInfo findOrMakeFolder(long id) {
+ // See if a placeholder was created for us already
+ FolderInfo folderInfo = folders.get(id);
+ if (folderInfo == null) {
+ // No placeholder -- create a new instance
+ folderInfo = new FolderInfo();
+ folders.put(id, folderInfo);
+ }
+ return folderInfo;
+ }
+
+ /**
+ * Clear all the deep shortcuts for the given package, and re-add the new shortcuts.
+ */
+ public synchronized void updateDeepShortcutMap(
+ String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
+ if (packageName != null) {
+ Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
+ while (keysIter.hasNext()) {
+ ComponentKey next = keysIter.next();
+ if (next.componentName.getPackageName().equals(packageName)
+ && next.user.equals(user)) {
+ keysIter.remove();
+ }
+ }
+ }
+
+ // Now add the new shortcuts to the map.
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ boolean shouldShowInContainer = shortcut.isEnabled()
+ && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+ if (shouldShowInContainer) {
+ ComponentKey targetComponent
+ = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+ deepShortcutMap.addToList(targetComponent, shortcut.getId());
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
new file mode 100644
index 0000000..9f24e90
--- /dev/null
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Handles changes due to cache updates.
+ */
+public class CacheDataUpdatedTask extends ExtendedModelTask {
+
+ public static final int OP_CACHE_UPDATE = 1;
+ public static final int OP_SESSION_UPDATE = 2;
+
+ private final int mOp;
+ private final UserHandleCompat mUser;
+ private final HashSet<String> mPackages;
+
+ public CacheDataUpdatedTask(int op, UserHandleCompat user, HashSet<String> packages) {
+ mOp = op;
+ mUser = user;
+ mPackages = packages;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ IconCache iconCache = app.getIconCache();
+
+ final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+
+ ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.getTargetComponent();
+ if (isValidShortcut(si) &&
+ cn != null && mPackages.contains(cn.getPackageName())) {
+ si.updateIcon(iconCache);
+ updatedShortcuts.add(si);
+ }
+ }
+ }
+ apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+ }
+ bindUpdatedShortcuts(updatedShortcuts, mUser);
+
+ if (!updatedApps.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppsUpdated(updatedApps);
+ }
+ });
+ }
+ }
+
+ public boolean isValidShortcut(ShortcutInfo si) {
+ switch (mOp) {
+ case OP_CACHE_UPDATE:
+ return si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ case OP_SESSION_UPDATE:
+ return si.isPromise();
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java
new file mode 100644
index 0000000..ccc6007
--- /dev/null
+++ b/src/com/android/launcher3/model/ExtendedModelTask.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link BaseModelUpdateTask} with some utility methods
+ */
+public abstract class ExtendedModelTask extends BaseModelUpdateTask {
+
+ public void bindUpdatedShortcuts(
+ ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
+ bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+ }
+
+ public void bindUpdatedShortcuts(
+ final ArrayList<ShortcutInfo> updatedShortcuts,
+ final ArrayList<ShortcutInfo> removedShortcuts,
+ final UserHandleCompat user) {
+ if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
+ }
+ });
+ }
+ }
+
+ public void bindDeepShortcuts(BgDataModel dataModel) {
+ final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindDeepShortcutMap(shortcutMapCopy);
+ }
+ });
+ }
+}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index fd647c7..599dcd0 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -42,7 +42,7 @@
*/
public class GridSizeMigrationTask {
- public static boolean ENABLED = Utilities.isNycOrAbove();
+ public static boolean ENABLED = Utilities.ATLEAST_NOUGAT;
private static final String TAG = "GridSizeMigrationTask";
private static final boolean DEBUG = true;
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
new file mode 100644
index 0000000..5d04325
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.HashSet;
+
+/**
+ * Handles changes due to a sessions updates for a currently installing app.
+ */
+public class PackageInstallStateChangedTask extends ExtendedModelTask {
+
+ private final PackageInstallInfo mInstallInfo;
+
+ public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
+ mInstallInfo = installInfo;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
+ // Ignore install success events as they are handled by Package add events.
+ return;
+ }
+
+ synchronized (dataModel) {
+ final HashSet<ItemInfo> updates = new HashSet<>();
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.getTargetComponent();
+ if (si.isPromise() && (cn != null)
+ && mInstallInfo.packageName.equals(cn.getPackageName())) {
+ si.setInstallProgress(mInstallInfo.progress);
+
+ if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+ // Mark this info as broken.
+ si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+ }
+ updates.add(si);
+ }
+ }
+ }
+
+ for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
+ if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
+ widget.installProgress = mInstallInfo.progress;
+ updates.add(widget);
+ }
+ }
+
+ if (!updates.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindRestoreItemsChange(updates);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index c86ba86..00470e1 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -40,12 +40,6 @@
*/
public String packageName;
- /**
- * Character that is used as a section name for the {@link ItemInfo#title}.
- * (e.g., "G" will be stored if title is "Google")
- */
- public String titleSectionName;
-
PackageItemInfo(String packageName) {
this.packageName = packageName;
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
new file mode 100644
index 0000000..176e8ea
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.util.FlagOp;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.ManagedProfileHeuristic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Handles updates due to changes in package manager (app installed/updated/removed)
+ * or when a user availability changes.
+ */
+public class PackageUpdatedTask extends ExtendedModelTask {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "PackageUpdatedTask";
+
+ public static final int OP_NONE = 0;
+ public static final int OP_ADD = 1;
+ public static final int OP_UPDATE = 2;
+ public static final int OP_REMOVE = 3; // uninstalled
+ public static final int OP_UNAVAILABLE = 4; // external media unmounted
+ public static final int OP_SUSPEND = 5; // package suspended
+ public static final int OP_UNSUSPEND = 6; // package unsuspended
+ public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
+
+ private final int mOp;
+ private final UserHandleCompat mUser;
+ private final String[] mPackages;
+
+ public PackageUpdatedTask(int op, UserHandleCompat user, String... packages) {
+ mOp = op;
+ mUser = user;
+ mPackages = packages;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+ final Context context = app.getContext();
+ final IconCache iconCache = app.getIconCache();
+
+ final String[] packages = mPackages;
+ final int N = packages.length;
+ FlagOp flagOp = FlagOp.NO_OP;
+ final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
+ ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+ switch (mOp) {
+ case OP_ADD: {
+ for (int i = 0; i < N; i++) {
+ if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+ iconCache.updateIconsForPkg(packages[i], mUser);
+ appsList.addPackage(context, packages[i], mUser);
+ }
+
+ 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) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+ iconCache.updateIconsForPkg(packages[i], mUser);
+ appsList.updatePackage(context, packages[i], mUser);
+ app.getWidgetCache().removePackage(packages[i], mUser);
+ }
+ // Since package was just updated, the target must be available now.
+ flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+ break;
+ case OP_REMOVE: {
+ ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+ if (heuristic != null) {
+ heuristic.processPackageRemoved(mPackages);
+ }
+ for (int i = 0; i < N; i++) {
+ iconCache.removeIconsForPkg(packages[i], mUser);
+ }
+ // Fall through
+ }
+ case OP_UNAVAILABLE:
+ for (int i = 0; i < N; i++) {
+ if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+ appsList.removePackage(packages[i], mUser);
+ app.getWidgetCache().removePackage(packages[i], mUser);
+ }
+ flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+ break;
+ case OP_SUSPEND:
+ case OP_UNSUSPEND:
+ flagOp = mOp == OP_SUSPEND ?
+ FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
+ FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
+ if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
+ appsList.updateDisabledFlags(matcher, flagOp);
+ break;
+ case OP_USER_AVAILABILITY_CHANGE:
+ flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
+ ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
+ : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
+ // We want to update all packages for this user.
+ matcher = ItemInfoMatcher.ofUser(mUser);
+ appsList.updateDisabledFlags(matcher, flagOp);
+ break;
+ }
+
+ ArrayList<AppInfo> added = null;
+ ArrayList<AppInfo> modified = null;
+ final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
+
+ if (appsList.added.size() > 0) {
+ added = new ArrayList<>(appsList.added);
+ appsList.added.clear();
+ }
+ if (appsList.modified.size() > 0) {
+ modified = new ArrayList<>(appsList.modified);
+ appsList.modified.clear();
+ }
+ if (appsList.removed.size() > 0) {
+ removedApps.addAll(appsList.removed);
+ appsList.removed.clear();
+ }
+
+ final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
+
+ if (added != null) {
+ final ArrayList<AppInfo> addedApps = added;
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppsAdded(null, null, null, addedApps);
+ }
+ });
+ for (AppInfo ai : added) {
+ addedOrUpdatedApps.put(ai.componentName, ai);
+ }
+ }
+
+ if (modified != null) {
+ final ArrayList<AppInfo> modifiedFinal = modified;
+ for (AppInfo ai : modified) {
+ addedOrUpdatedApps.put(ai.componentName, ai);
+ }
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppsUpdated(modifiedFinal);
+ }
+ });
+ }
+
+ // Update shortcut infos
+ if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
+ final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+ final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
+ final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ boolean infoUpdated = false;
+ boolean shortcutUpdated = false;
+
+ // Update shortcuts which use iconResource.
+ if ((si.iconResource != null)
+ && packageSet.contains(si.iconResource.packageName)) {
+ Bitmap icon = LauncherIcons.createIconBitmap(
+ si.iconResource.packageName,
+ si.iconResource.resourceName, context);
+ if (icon != null) {
+ si.setIcon(icon);
+ infoUpdated = true;
+ }
+ }
+
+ ComponentName cn = si.getTargetComponent();
+ if (cn != null && matcher.matches(si, cn)) {
+ AppInfo appInfo = addedOrUpdatedApps.get(cn);
+
+ if (si.isPromise() && mOp == OP_ADD) {
+ if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+ // Auto install icon
+ PackageManager pm = context.getPackageManager();
+ ResolveInfo matched = pm.resolveActivity(
+ new Intent(Intent.ACTION_MAIN)
+ .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (matched == null) {
+ // Try to find the best match activity.
+ Intent intent = pm.getLaunchIntentForPackage(
+ cn.getPackageName());
+ if (intent != null) {
+ cn = intent.getComponent();
+ appInfo = addedOrUpdatedApps.get(cn);
+ }
+
+ if ((intent == null) || (appInfo == null)) {
+ removedShortcuts.add(si);
+ continue;
+ }
+ si.promisedIntent = intent;
+ }
+ }
+
+ si.intent = si.promisedIntent;
+ si.promisedIntent = null;
+ si.status = ShortcutInfo.DEFAULT;
+ infoUpdated = true;
+ si.updateIcon(iconCache);
+ }
+
+ if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
+ && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ si.updateIcon(iconCache);
+ si.title = Utilities.trim(appInfo.title);
+ si.contentDescription = appInfo.contentDescription;
+ infoUpdated = true;
+ }
+
+ int oldDisabledFlags = si.isDisabled;
+ si.isDisabled = flagOp.apply(si.isDisabled);
+ if (si.isDisabled != oldDisabledFlags) {
+ shortcutUpdated = true;
+ }
+ }
+
+ if (infoUpdated || shortcutUpdated) {
+ updatedShortcuts.add(si);
+ }
+ if (infoUpdated) {
+ LauncherModel.updateItemInDatabase(context, si);
+ }
+ } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
+ LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
+ if (mUser.equals(widgetInfo.user)
+ && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+ && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+ widgetInfo.restoreStatus &=
+ ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
+ ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+ // adding this flag ensures that launcher shows 'click to setup'
+ // if the widget has a config activity. In case there is no config
+ // activity, it will be marked as 'restored' during bind.
+ widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+ widgets.add(widgetInfo);
+ LauncherModel.updateItemInDatabase(context, widgetInfo);
+ }
+ }
+ }
+ }
+
+ bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
+ if (!removedShortcuts.isEmpty()) {
+ LauncherModel.deleteItemsFromDatabase(context, removedShortcuts);
+ }
+
+ if (!widgets.isEmpty()) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindWidgetsRestored(widgets);
+ }
+ });
+ }
+ }
+
+ final HashSet<String> removedPackages = new HashSet<>();
+ final HashSet<ComponentName> removedComponents = new HashSet<>();
+ if (mOp == OP_REMOVE) {
+ // Mark all packages in the broadcast to be removed
+ Collections.addAll(removedPackages, packages);
+
+ // No need to update the removedComponents as
+ // removedPackages is a super-set of removedComponents
+ } else if (mOp == OP_UPDATE) {
+ // Mark disabled packages in the broadcast to be removed
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ for (int i=0; i<N; i++) {
+ if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
+ removedPackages.add(packages[i]);
+ }
+ }
+
+ // Update removedComponents as some components can get removed during package update
+ for (AppInfo info : removedApps) {
+ removedComponents.add(info.componentName);
+ }
+ }
+
+ if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
+ LauncherModel.deleteItemsFromDatabase(
+ context, ItemInfoMatcher.ofPackages(removedPackages, mUser));
+ LauncherModel.deleteItemsFromDatabase(
+ context, ItemInfoMatcher.ofComponents(removedComponents, mUser));
+
+ // Remove any queued items from the install queue
+ InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+
+ // Call the components-removed callback
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindWorkspaceComponentsRemoved(
+ removedPackages, removedComponents, mUser);
+ }
+ });
+ }
+
+ if (!removedApps.isEmpty()) {
+ // Remove corresponding apps from All-Apps
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.bindAppInfosRemoved(removedApps);
+ }
+ });
+ }
+
+ // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
+ // get widget update signals.
+ if (!Utilities.ATLEAST_MARSHMALLOW &&
+ (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
+ scheduleCallbackTask(new CallbackTask() {
+ @Override
+ public void execute(Callbacks callbacks) {
+ callbacks.notifyWidgetProvidersChanged();
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
new file mode 100644
index 0000000..54260c9
--- /dev/null
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
+/**
+ * Helper class to re-query app status when SD-card becomes available.
+ *
+ * During first load, just after reboot, some apps on sdcard might not be available immediately due
+ * to some race conditions in the system. We wait for ACTION_BOOT_COMPLETED and process such
+ * apps again.
+ */
+public class SdCardAvailableReceiver extends BroadcastReceiver {
+
+ private final LauncherModel mModel;
+ private final Context mContext;
+ private final MultiHashMap<UserHandleCompat, String> mPackages;
+
+ public SdCardAvailableReceiver(LauncherModel model, Context context,
+ MultiHashMap<UserHandleCompat, String> packages) {
+ mModel = model;
+ mContext = context;
+ mPackages = packages;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ final PackageManager manager = context.getPackageManager();
+ for (Entry<UserHandleCompat, ArrayList<String>> entry : mPackages.entrySet()) {
+ UserHandleCompat user = entry.getKey();
+
+ final ArrayList<String> packagesRemoved = new ArrayList<>();
+ final ArrayList<String> packagesUnavailable = new ArrayList<>();
+
+ for (String pkg : new HashSet<>(entry.getValue())) {
+ if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
+ if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
+ packagesUnavailable.add(pkg);
+ } else {
+ packagesRemoved.add(pkg);
+ }
+ }
+ }
+ if (!packagesRemoved.isEmpty()) {
+ mModel.onPackagesRemoved(user,
+ packagesRemoved.toArray(new String[packagesRemoved.size()]));
+ }
+ if (!packagesUnavailable.isEmpty()) {
+ mModel.onPackagesUnavailable(
+ packagesUnavailable.toArray(new String[packagesUnavailable.size()]),
+ user, false);
+ }
+ }
+
+ // Unregister the broadcast receiver, just in case
+ mContext.unregisterReceiver(this);
+ }
+}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
new file mode 100644
index 0000000..3314353
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles changes due to shortcut manager updates (deep shortcut changes)
+ */
+public class ShortcutsChangedTask extends ExtendedModelTask {
+
+ private final String mPackageName;
+ private final List<ShortcutInfoCompat> mShortcuts;
+ private final UserHandleCompat mUser;
+ private final boolean mUpdateIdMap;
+
+ public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+ UserHandleCompat user, boolean updateIdMap) {
+ mPackageName = packageName;
+ mShortcuts = shortcuts;
+ mUser = user;
+ mUpdateIdMap = updateIdMap;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ final Context context = app.getContext();
+ DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+ deepShortcutManager.onShortcutsChanged(mShortcuts);
+
+ // Find ShortcutInfo's that have changed on the workspace.
+ final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
+ MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+ for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+ if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ ShortcutInfo si = (ShortcutInfo) itemInfo;
+ if (si.getPromisedIntent().getPackage().equals(mPackageName)
+ && si.user.equals(mUser)) {
+ idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
+ }
+ }
+ }
+
+ final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+ if (!idsToWorkspaceShortcutInfos.isEmpty()) {
+ // Update the workspace to reflect the changes to updated shortcuts residing on it.
+ List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails(
+ mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+ for (ShortcutInfoCompat fullDetails : shortcuts) {
+ List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+ .remove(fullDetails.getId());
+ if (!fullDetails.isPinned()) {
+ // The shortcut was previously pinned but is no longer, so remove it from
+ // the workspace and our pinned shortcut counts.
+ // Note that we put this check here, after querying for full details,
+ // because there's a possible race condition between pinning and
+ // receiving this callback.
+ removedShortcutInfos.addAll(shortcutInfos);
+ continue;
+ }
+ for (ShortcutInfo shortcutInfo : shortcutInfos) {
+ shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
+ updatedShortcutInfos.add(shortcutInfo);
+ }
+ }
+ }
+
+ // If there are still entries in idsToWorkspaceShortcutInfos, that means that
+ // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
+ // means they were cleared, so we remove and unpin them now.
+ for (String id : idsToWorkspaceShortcutInfos.keySet()) {
+ removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
+ }
+
+ bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
+ if (!removedShortcutInfos.isEmpty()) {
+ LauncherModel.deleteItemsFromDatabase(context, removedShortcutInfos);
+ }
+
+ if (mUpdateIdMap) {
+ // Update the deep shortcut map if the list of ids has changed for an activity.
+ dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
+ bindDeepShortcuts(dataModel);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
new file mode 100644
index 0000000..a89fe0b
--- /dev/null
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Task to handle changing of lock state of the user
+ */
+public class UserLockStateChangedTask extends ExtendedModelTask {
+
+ private final UserHandleCompat mUser;
+
+ public UserLockStateChangedTask(UserHandleCompat user) {
+ mUser = user;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ Context context = app.getContext();
+ boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
+ DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+
+ HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
+ if (isUserUnlocked) {
+ List<ShortcutInfoCompat> shortcuts =
+ deepShortcutManager.queryForPinnedShortcuts(null, mUser);
+ if (deepShortcutManager.wasLastCallSuccess()) {
+ for (ShortcutInfoCompat shortcut : shortcuts) {
+ pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
+ }
+ } else {
+ // Shortcut manager can fail due to some race condition when the lock state
+ // changes too frequently. For the purpose of the update,
+ // consider it as still locked.
+ isUserUnlocked = false;
+ }
+ }
+
+ // Update the workspace to reflect the changes to updated shortcuts residing on it.
+ ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+ ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
+ for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+ if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && mUser.equals(itemInfo.user)) {
+ ShortcutInfo si = (ShortcutInfo) itemInfo;
+ if (isUserUnlocked) {
+ ShortcutInfoCompat shortcut =
+ pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
+ // We couldn't verify the shortcut during loader. If its no longer available
+ // (probably due to clear data), delete the workspace item as well
+ if (shortcut == null) {
+ deletedShortcutInfos.add(si);
+ continue;
+ }
+ si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ si.updateFromDeepShortcutInfo(shortcut, context);
+ } else {
+ si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+ }
+ updatedShortcutInfos.add(si);
+ }
+ }
+ bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
+ if (!deletedShortcutInfos.isEmpty()) {
+ LauncherModel.deleteItemsFromDatabase(context, deletedShortcutInfos);
+ }
+
+ // Remove shortcut id map for that user
+ Iterator<ComponentKey> keysIter = dataModel.deepShortcutMap.keySet().iterator();
+ while (keysIter.hasNext()) {
+ if (keysIter.next().user.equals(mUser)) {
+ keysIter.remove();
+ }
+ }
+
+ if (isUserUnlocked) {
+ dataModel.updateDeepShortcutMap(
+ null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
+ }
+ bindDeepShortcuts(dataModel);
+ }
+}
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index b2a94bb..64043f4 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -6,26 +6,22 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
import android.util.Log;
import com.android.launcher3.AppFilter;
import com.android.launcher3.IconCache;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.Preconditions;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.List;
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
@@ -37,74 +33,31 @@
private static final String TAG = "WidgetsModel";
private static final boolean DEBUG = false;
- /* List of packages that is tracked by this model. */
- private final ArrayList<PackageItemInfo> mPackageItemInfos;
-
/* Map of widgets and shortcuts that are tracked per package. */
- private final HashMap<PackageItemInfo, ArrayList<WidgetItem>> mWidgetsList;
+ private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList;
- private final AppWidgetManagerCompat mAppWidgetMgr;
- private final Comparator<ItemInfo> mAppNameComparator;
private final IconCache mIconCache;
private final AppFilter mAppFilter;
- private final AlphabeticIndexCompat mIndexer;
- private ArrayList<WidgetItem> mRawList;
-
- public WidgetsModel(Context context, IconCache iconCache, AppFilter appFilter) {
- mAppWidgetMgr = AppWidgetManagerCompat.getInstance(context);
- mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator();
+ public WidgetsModel(IconCache iconCache, AppFilter appFilter) {
mIconCache = iconCache;
mAppFilter = appFilter;
- mIndexer = new AlphabeticIndexCompat(context);
- mPackageItemInfos = new ArrayList<>();
- mWidgetsList = new HashMap<>();
-
- mRawList = new ArrayList<>();
+ mWidgetsList = new MultiHashMap<>();
}
- @SuppressWarnings("unchecked")
- private WidgetsModel(WidgetsModel model) {
- mAppWidgetMgr = model.mAppWidgetMgr;
- mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
- mWidgetsList = (HashMap<PackageItemInfo, ArrayList<WidgetItem>>) model.mWidgetsList.clone();
- mAppNameComparator = model.mAppNameComparator;
- mIconCache = model.mIconCache;
- mAppFilter = model.mAppFilter;
- mIndexer = model.mIndexer;
- mRawList = (ArrayList<WidgetItem>) model.mRawList.clone();
- }
-
- // 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) {
- if (pos >= mPackageItemInfos.size() || pos < 0) {
- return null;
- }
- return mPackageItemInfos.get(pos);
- }
-
- public List<WidgetItem> getSortedWidgets(int pos) {
- return mWidgetsList.get(mPackageItemInfos.get(pos));
- }
-
- public ArrayList<WidgetItem> getRawList() {
- return mRawList;
+ public MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() {
+ return mWidgetsList;
}
public boolean isEmpty() {
- return mRawList.isEmpty();
+ return mWidgetsList.isEmpty();
}
- public WidgetsModel updateAndClone(Context context) {
+ public ArrayList<WidgetItem> update(Context context) {
Preconditions.assertWorkerThread();
+ final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
try {
- final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
// Widgets
AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context);
for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders()) {
@@ -121,9 +74,7 @@
}
setWidgetsAndShortcuts(widgetsAndShortcuts);
} catch (Exception e) {
- if (!ProviderConfig.IS_DOGFOOD_BUILD &&
- (e.getCause() instanceof TransactionTooLargeException ||
- e.getCause() instanceof DeadObjectException)) {
+ if (!ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
// the returned value may be incomplete and will not be refreshed until the next
// time Launcher starts.
// TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -132,11 +83,10 @@
throw e;
}
}
- return clone();
+ return widgetsAndShortcuts;
}
private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts) {
- mRawList = rawWidgetsShortcuts;
if (DEBUG) {
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
@@ -147,9 +97,9 @@
// clear the lists.
mWidgetsList.clear();
- mPackageItemInfos.clear();
InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ UserHandleCompat myUser = UserHandleCompat.myUserHandle();
// add and update.
for (WidgetItem item: rawWidgetsShortcuts) {
@@ -177,43 +127,20 @@
String packageName = item.componentName.getPackageName();
PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
- ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(pInfo);
-
- if (widgetsShortcutsList == null) {
- widgetsShortcutsList = new ArrayList<>();
-
+ if (pInfo == null) {
pInfo = new PackageItemInfo(packageName);
+ pInfo.user = item.user;
tmpPackageItemInfos.put(packageName, pInfo);
-
- mPackageItemInfos.add(pInfo);
- mWidgetsList.put(pInfo, widgetsShortcutsList);
+ } else if (!myUser.equals(pInfo.user)) {
+ // Keep updating the user, until we get the primary user.
+ pInfo.user = item.user;
}
-
- widgetsShortcutsList.add(item);
+ mWidgetsList.addToList(pInfo, item);
}
// Update each package entry
- for (PackageItemInfo p : mPackageItemInfos) {
- ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(p);
- Collections.sort(widgetsShortcutsList);
-
- // Update the package entry based on the first item.
- p.user = widgetsShortcutsList.get(0).user;
+ for (PackageItemInfo p : tmpPackageItemInfos.values()) {
mIconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
- p.titleSectionName = mIndexer.computeSectionName(p.title);
}
-
- // sort the package entries.
- Collections.sort(mPackageItemInfos, mAppNameComparator);
- }
-
- /**
- * Create a snapshot of the widgets model.
- * <p>
- * Usage case: view binding without being modified from package updates.
- */
- @Override
- public WidgetsModel clone(){
- return new WidgetsModel(this);
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index fb9d2f7..12a6701 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -80,7 +80,7 @@
@Override
public void onAnimationEnd(Animator animation) {
mAnimator = null;
- animateToPostion(mFinalPosition);
+ animateToPosition(mFinalPosition);
}
};
@@ -136,22 +136,25 @@
currentScroll = totalScroll - currentScroll;
}
int scrollPerPage = totalScroll / (mNumPages - 1);
- int absScroll = mActivePage * scrollPerPage;
- float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ int pageToLeft = currentScroll / scrollPerPage;
+ int pageToLeftScroll = pageToLeft * scrollPerPage;
+ int pageToRightScroll = pageToLeftScroll + scrollPerPage;
- if ((absScroll - currentScroll) > scrollThreshold) {
- // current scroll is before absolute scroll
- animateToPostion(mActivePage - SHIFT_PER_ANIMATION);
- } else if ((currentScroll - absScroll) > scrollThreshold) {
- // current scroll is ahead of absolute scroll
- animateToPostion(mActivePage + SHIFT_PER_ANIMATION);
+ float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ if (currentScroll < pageToLeftScroll + scrollThreshold) {
+ // scroll is within the left page's threshold
+ animateToPosition(pageToLeft);
+ } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+ // scroll is far enough from left page to go to the right page
+ animateToPosition(pageToLeft + 1);
} else {
- animateToPostion(mActivePage);
+ // scroll is between left and right page
+ animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
}
}
}
- private void animateToPostion(float position) {
+ private void animateToPosition(float position) {
mFinalPosition = position;
if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
mCurrentPosition = mFinalPosition;
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index faa5fad..2f82419 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -56,7 +56,7 @@
if (screenIds.get(0) != 0) {
// First screen is not 0, we need to rename screens
if (screenIds.indexOf(0L) > -1) {
- // There is already a screen 0. First rename it to a differen screen.
+ // There is already a screen 0. First rename it to a different screen.
long newScreenId = 1;
while (screenIds.indexOf(newScreenId) > -1) newScreenId++;
renameScreen(db, 0, newScreenId);
diff --git a/src/com/android/launcher3/QsbBlockerView.java b/src/com/android/launcher3/qsb/QsbBlockerView.java
similarity index 91%
rename from src/com/android/launcher3/QsbBlockerView.java
rename to src/com/android/launcher3/qsb/QsbBlockerView.java
index 6a2bce0..5379336 100644
--- a/src/com/android/launcher3/QsbBlockerView.java
+++ b/src/com/android/launcher3/qsb/QsbBlockerView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.qsb;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
@@ -27,12 +27,15 @@
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.Workspace.OnStateChangeListener;
import com.android.launcher3.Workspace.State;
/**
* A simple view used to show the region blocked by QSB during drag and drop.
*/
-public class QsbBlockerView extends View implements Workspace.OnStateChangeListener {
+public class QsbBlockerView extends View implements OnStateChangeListener {
private static final int VISIBLE_ALPHA = 100;
diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
similarity index 61%
rename from src/com/android/launcher3/QsbContainerView.java
rename to src/com/android/launcher3/qsb/QsbContainerView.java
index 02d8a13..c83143b 100644
--- a/src/com/android/launcher3/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -14,19 +14,18 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.qsb;
import android.app.Activity;
import android.app.Fragment;
import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -35,6 +34,11 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
/**
@@ -68,25 +72,14 @@
private static final int REQUEST_BIND_QSB = 1;
private static final String QSB_WIDGET_ID = "qsb_widget_id";
- private static int sSavedWidgetId = -1;
-
+ private QsbWidgetHost mQsbWidgetHost;
private AppWidgetProviderInfo mWidgetInfo;
- private LauncherAppWidgetHostView mQsb;
-
- private BroadcastReceiver mRebindReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- rebindFragment();
- }
- };
+ private QsbWidgetHostView mQsb;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- IntentFilter filter = new IntentFilter(Launcher.ACTION_APPWIDGET_HOST_RESET);
- filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- getActivity().registerReceiver(mRebindReceiver, filter);
+ mQsbWidgetHost = new QsbWidgetHost(getActivity());
}
private FrameLayout mWrapper;
@@ -95,108 +88,96 @@
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- sSavedWidgetId = savedInstanceState.getInt(QSB_WIDGET_ID, -1);
- }
mWrapper = new FrameLayout(getActivity());
- mWrapper.addView(createQsb(inflater, mWrapper));
+ mWrapper.addView(createQsb(mWrapper));
return mWrapper;
}
- private View createQsb(LayoutInflater inflater, ViewGroup container) {
- Launcher launcher = Launcher.getLauncher(getActivity());
- mWidgetInfo = getSearchWidgetProvider(launcher);
+ private View createQsb(ViewGroup container) {
+ Activity activity = getActivity();
+ mWidgetInfo = getSearchWidgetProvider(activity);
if (mWidgetInfo == null) {
// There is no search provider, just show the default widget.
- return getDefaultView(inflater, container, false);
+ return QsbWidgetHostView.getDefaultView(container);
}
- SharedPreferences prefs = Utilities.getPrefs(launcher);
- AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(launcher);
- LauncherAppWidgetHost widgetHost = launcher.getAppWidgetHost();
+ AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(activity);
InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
Bundle opts = new Bundle();
- Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(launcher, idp.numColumns, 1, null);
+ Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(activity, idp.numColumns, 1, null);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
- int widgetId = prefs.getInt(QSB_WIDGET_ID, -1);
+ int widgetId = Utilities.getPrefs(activity).getInt(QSB_WIDGET_ID, -1);
AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
boolean isWidgetBound = (widgetInfo != null) &&
widgetInfo.provider.equals(mWidgetInfo.provider);
+ int oldWidgetId = widgetId;
if (!isWidgetBound) {
- // widgetId is already bound and its not the correct provider.
- // Delete the widget id.
if (widgetId > -1) {
- widgetHost.deleteAppWidgetId(widgetId);
+ // widgetId is already bound and its not the correct provider. reset host.
+ mQsbWidgetHost.deleteHost();
+ }
+
+ widgetId = mQsbWidgetHost.allocateAppWidgetId();
+ isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
+ if (!isWidgetBound) {
+ mQsbWidgetHost.deleteAppWidgetId(widgetId);
widgetId = -1;
}
- widgetId = widgetHost.allocateAppWidgetId();
- isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
- if (!isWidgetBound) {
- widgetHost.deleteAppWidgetId(widgetId);
- widgetId = -1;
+ if (oldWidgetId != widgetId) {
+ saveWidgetId(widgetId);
}
}
if (isWidgetBound) {
- mQsb = (LauncherAppWidgetHostView)
- widgetHost.createView(launcher, widgetId, mWidgetInfo);
+ mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(activity, widgetId, mWidgetInfo);
mQsb.setId(R.id.qsb_widget);
- mQsb.mErrorViewId = R.layout.qsb_default_view;
- if (!Utilities.containsAll(AppWidgetManager.getInstance(launcher)
+ if (!Utilities.containsAll(AppWidgetManager.getInstance(activity)
.getAppWidgetOptions(widgetId), opts)) {
mQsb.updateAppWidgetOptions(opts);
}
mQsb.setPadding(0, 0, 0, 0);
+ mQsbWidgetHost.startListening();
return mQsb;
}
// Return a default widget with setup icon.
- return getDefaultView(inflater, container, true);
+ View v = QsbWidgetHostView.getDefaultView(container);
+ View setupButton = v.findViewById(R.id.btn_qsb_setup);
+ setupButton.setVisibility(View.VISIBLE);
+ setupButton.setOnClickListener(this);
+ return v;
+ }
+
+ private void saveWidgetId(int widgetId) {
+ Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
}
@Override
public void onClick(View view) {
- if (view.getId() == R.id.btn_qsb_search) {
- getActivity().startSearch("", false, null, true);
- } else if (view.getId() == R.id.btn_qsb_setup) {
- // Allocate a new widget id for QSB
- sSavedWidgetId = Launcher.getLauncher(getActivity())
- .getAppWidgetHost().allocateAppWidgetId();
- // Start intent for bind the widget
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, sSavedWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
- startActivityForResult(intent, REQUEST_BIND_QSB);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(QSB_WIDGET_ID, sSavedWidgetId);
+ // Start intent for bind the widget
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+ // Allocate a new widget id for QSB
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId());
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
+ startActivityForResult(intent, REQUEST_BIND_QSB);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_BIND_QSB) {
if (resultCode == Activity.RESULT_OK) {
- int widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
- sSavedWidgetId);
- Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
- sSavedWidgetId = -1;
+ saveWidgetId(data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1));
rebindFragment();
- } else if (sSavedWidgetId != -1) {
- Launcher.getLauncher(getActivity()).getAppWidgetHost()
- .deleteAppWidgetId(sSavedWidgetId);
- sSavedWidgetId = -1;
+ } else {
+ mQsbWidgetHost.deleteHost();
}
}
}
@@ -211,27 +192,16 @@
@Override
public void onDestroy() {
- getActivity().unregisterReceiver(mRebindReceiver);
+ mQsbWidgetHost.stopListening();
super.onDestroy();
}
private void rebindFragment() {
if (mWrapper != null && getActivity() != null) {
mWrapper.removeAllViews();
- mWrapper.addView(createQsb(getActivity().getLayoutInflater(), mWrapper));
+ mWrapper.addView(createQsb(mWrapper));
}
}
-
- private View getDefaultView(LayoutInflater inflater, ViewGroup parent, boolean showSetup) {
- View v = inflater.inflate(R.layout.qsb_default_view, parent, false);
- if (showSetup) {
- View setupButton = v.findViewById(R.id.btn_qsb_setup);
- setupButton.setVisibility(View.VISIBLE);
- setupButton.setOnClickListener(this);
- }
- v.findViewById(R.id.btn_qsb_search).setOnClickListener(this);
- return v;
- }
}
/**
@@ -261,4 +231,19 @@
}
return defaultWidgetForSearchPackage;
}
+
+ private static class QsbWidgetHost extends AppWidgetHost {
+
+ private static final int QSB_WIDGET_HOST_ID = 1026;
+
+ public QsbWidgetHost(Context context) {
+ super(context, QSB_WIDGET_HOST_ID);
+ }
+
+ @Override
+ protected AppWidgetHostView onCreateView(
+ Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
+ return new QsbWidgetHostView(context);
+ }
+ }
}
diff --git a/src/com/android/launcher3/qsb/QsbWidgetHostView.java b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
new file mode 100644
index 0000000..8b6fa16
--- /dev/null
+++ b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.qsb;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * Appwidget host view with QSB specific logic.
+ */
+public class QsbWidgetHostView extends AppWidgetHostView {
+
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private int mPreviousOrientation;
+
+ public QsbWidgetHostView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ // Store the orientation in which the widget was inflated
+ mPreviousOrientation = getResources().getConfiguration().orientation;
+ super.updateAppWidget(remoteViews);
+ }
+
+
+ public boolean isReinflateRequired() {
+ // Re-inflate is required if the orientation has changed since last inflation.
+ return mPreviousOrientation != getResources().getConfiguration().orientation;
+ }
+
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ try {
+ super.onLayout(changed, left, top, right, bottom);
+ } catch (final RuntimeException e) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ // Update the widget with 0 Layout id, to reset the view to error view.
+ updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ }
+ });
+ }
+ }
+
+ @Override
+ protected View getErrorView() {
+ return getDefaultView(this);
+ }
+
+ public static View getDefaultView(ViewGroup parent) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.qsb_default_view, parent, false);
+ v.findViewById(R.id.btn_qsb_search).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Launcher.getLauncher(view.getContext()).startSearch("", false, null, true);
+ }
+ });
+ return v;
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 49d6fa9..41f1a47 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -42,18 +42,25 @@
public class DeepShortcutManager {
private static final String TAG = "DeepShortcutManager";
- // TODO: Replace this with platform constants when the new sdk is available.
- public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
- public static final int FLAG_MATCH_MANIFEST = 1 << 3;
- public static final int FLAG_MATCH_PINNED = 1 << 1;
+ private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
- private static final int FLAG_GET_ALL =
- FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+ private static DeepShortcutManager sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ public static DeepShortcutManager getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new DeepShortcutManager(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+ }
private final LauncherApps mLauncherApps;
private boolean mWasLastCallSuccess;
- public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
+ private DeepShortcutManager(Context context) {
mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
}
@@ -86,7 +93,7 @@
*/
public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
List<String> ids, UserHandleCompat user) {
- return query(FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC,
+ return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
activity.getPackageName(), activity, ids, user);
}
@@ -96,7 +103,7 @@
*/
@TargetApi(25)
public void unpinShortcut(final ShortcutKey key) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
String packageName = key.componentName.getPackageName();
String id = key.getId();
UserHandleCompat user = key.user;
@@ -118,7 +125,7 @@
*/
@TargetApi(25)
public void pinShortcut(final ShortcutKey key) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
String packageName = key.componentName.getPackageName();
String id = key.getId();
UserHandleCompat user = key.user;
@@ -137,7 +144,7 @@
@TargetApi(25)
public void startShortcut(String packageName, String id, Rect sourceBounds,
Bundle startActivityOptions, UserHandleCompat user) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
try {
mLauncherApps.startShortcut(packageName, id, sourceBounds,
startActivityOptions, user.getUser());
@@ -151,7 +158,7 @@
@TargetApi(25)
public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
try {
Drawable icon = mLauncherApps.getShortcutIconDrawable(
shortcutInfo.getShortcutInfo(), density);
@@ -172,7 +179,7 @@
*/
public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
UserHandleCompat user) {
- return query(FLAG_MATCH_PINNED, packageName, null, null, user);
+ return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
}
public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
@@ -196,7 +203,7 @@
@TargetApi(25)
private List<ShortcutInfoCompat> query(int flags, String packageName,
ComponentName activity, List<String> shortcutIds, UserHandleCompat user) {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
ShortcutQuery q = new ShortcutQuery();
q.setQueryFlags(flags);
if (packageName != null) {
@@ -227,7 +234,7 @@
@TargetApi(25)
public boolean hasHostPermission() {
- if (Utilities.isNycMR1OrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT_MR1) {
try {
return mLauncherApps.hasShortcutHostPermission();
} catch (SecurityException|IllegalStateException e) {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 2702d4e..314a862 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
@@ -27,6 +28,7 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
@@ -37,10 +39,12 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -48,7 +52,6 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherViewPropertyAnimator;
@@ -73,9 +76,9 @@
* A container for shortcuts to deep links within apps.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener,
+public class DeepShortcutsContainer extends AbstractFloatingView
+ implements View.OnLongClickListener,
View.OnTouchListener, DragSource, DragController.DragListener {
- private static final String TAG = "ShortcutsContainer";
private final Point mIconShift = new Point();
@@ -85,8 +88,9 @@
private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
private final boolean mIsRtl;
- private BubbleTextView mDeferredDragIcon;
+ private BubbleTextView mOriginalIcon;
private final Rect mTempRect = new Rect();
+ private PointF mInterceptTouchDown = new PointF();
private Point mIconLastTouchPos = new Point();
private boolean mIsLeftAligned;
private boolean mIsAboveIcon;
@@ -94,12 +98,11 @@
private Animator mOpenCloseAnimator;
private boolean mDeferContainerRemoval;
- private boolean mIsOpen;
public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
- mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
+ mDeepShortcutsManager = DeepShortcutManager.getInstance(context);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
@@ -150,7 +153,8 @@
animateOpen();
- deferDrag(originalIcon);
+ mOriginalIcon = originalIcon;
+ mLauncher.getDragController().addDragListener(this);
// Load the shortcuts on a background thread and update the container as it animates.
final Looper workerLooper = LauncherModel.getWorkerLooper();
@@ -375,13 +379,9 @@
return arrowView;
}
- private void deferDrag(BubbleTextView originalIcon) {
- mDeferredDragIcon = originalIcon;
- mLauncher.getDragController().addDragListener(this);
- }
-
- public BubbleTextView getDeferredDragIcon() {
- return mDeferredDragIcon;
+ @Override
+ public View getExtendedTouchView() {
+ return mOriginalIcon;
}
/**
@@ -390,36 +390,52 @@
* Current behavior:
* - Start the drag if the touch passes a certain distance from the original touch down.
*/
- public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
- return new DragOptions.DeferDragCondition() {
+ public DragOptions.PreDragCondition createPreDragCondition() {
+ return new DragOptions.PreDragCondition() {
@Override
- public boolean shouldStartDeferredDrag(double distanceDragged) {
+ public boolean shouldStartDrag(double distanceDragged) {
return distanceDragged > mStartDragThreshold;
}
@Override
- public void onDeferredDragStart() {
- mDeferredDragIcon.setVisibility(INVISIBLE);
+ public void onPreDragStart() {
+ mOriginalIcon.setVisibility(INVISIBLE);
}
@Override
- public void onDropBeforeDeferredDrag() {
- mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
- if (!mIsAboveIcon) {
- mDeferredDragIcon.setTextVisibility(false);
- }
- }
-
- @Override
- public void onDragStart() {
- if (onDragStart != null) {
- onDragStart.run();
+ public void onPreDragEnd(boolean dragStarted) {
+ if (!dragStarted) {
+ mOriginalIcon.setVisibility(VISIBLE);
+ mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
+ if (!mIsAboveIcon) {
+ mOriginalIcon.setTextVisibility(false);
+ }
}
}
};
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mInterceptTouchDown.set(ev.getX(), ev.getY());
+ return false;
+ }
+ // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
+ return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
+ > ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ /**
+ * We need to handle touch events to prevent them from falling through to the workspace below.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return true;
+ }
+
+ @Override
public boolean onTouch(View v, MotionEvent ev) {
// Touched a shortcut, update where it was touched so we can drag from there on long click.
switch (ev.getAction()) {
@@ -434,8 +450,10 @@
public boolean onLongClick(View v) {
// Return early if this is not initiated from a touch or not the correct view
if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
- // Return if global dragging is not enabled
+ // Return early if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return false;
+ // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+ if (mLauncher.getDragController().isDragging()) return false;
// Long clicked on a shortcut.
mDeferContainerRemoval = true;
@@ -452,7 +470,7 @@
dv.animateShift(-mIconShift.x, -mIconShift.y);
// TODO: support dragging from within folder without having to close it
- mLauncher.closeFolder();
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
return false;
}
@@ -507,21 +525,29 @@
} else {
// Close animation is not running.
if (mDeferContainerRemoval) {
- close();
+ closeComplete();
}
}
}
- mDeferredDragIcon.setVisibility(VISIBLE);
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
target.itemType = LauncherLogProto.DEEPSHORTCUT;
// TODO: add target.rank
targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS;
}
- public void animateClose() {
+ @Override
+ protected void handleClose(boolean animate) {
+ if (animate) {
+ animateClose();
+ } else {
+ closeComplete();
+ }
+ }
+
+ private void animateClose() {
if (!mIsOpen) {
return;
}
@@ -601,7 +627,7 @@
if (mDeferContainerRemoval) {
setVisibility(INVISIBLE);
} else {
- close();
+ closeComplete();
}
}
});
@@ -609,25 +635,30 @@
shortcutAnims.start();
}
+ public ShortcutMenuAccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
/**
* Closes the folder without animation.
*/
- public void close() {
+ private void closeComplete() {
if (mOpenCloseAnimator != null) {
mOpenCloseAnimator.cancel();
mOpenCloseAnimator = null;
}
mIsOpen = false;
mDeferContainerRemoval = false;
- boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
+ boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT;
- mDeferredDragIcon.setTextVisibility(!isInHotseat);
+ mOriginalIcon.setTextVisibility(!isInHotseat);
mLauncher.getDragController().removeDragListener(this);
mLauncher.getDragLayer().removeView(this);
}
- public boolean isOpen() {
- return mIsOpen;
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_DEEPSHORTCUT_CONTAINER) != 0;
}
/**
@@ -636,14 +667,13 @@
*/
public static DeepShortcutsContainer showForIcon(BubbleTextView icon) {
Launcher launcher = Launcher.getLauncher(icon.getContext());
- if (launcher.getOpenShortcutsContainer() != null) {
+ if (getOpen(launcher) != null) {
// There is already a shortcuts container open, so don't open this one.
icon.clearFocus();
return null;
}
List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
if (!ids.isEmpty()) {
- // There are shortcuts associated with the app, so defer its drag.
final DeepShortcutsContainer container =
(DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
R.layout.deep_shortcuts_container, launcher.getDragLayer(), false);
@@ -672,4 +702,11 @@
return unbadgedBitmap;
}
}
+
+ /**
+ * Returns a DeepShortcutsContainer which is already open or null
+ */
+ public static DeepShortcutsContainer getOpen(Launcher launcher) {
+ return getOpenView(launcher, TYPE_DEEPSHORTCUT_CONTAINER);
+ }
}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 2adb82e..fc474f5 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,7 +23,7 @@
import android.graphics.drawable.Drawable;
import android.view.View;
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -44,7 +44,7 @@
public Bitmap createDragOutline(Canvas canvas) {
Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8);
- HolographicOutlineHelper.obtain(mView.getContext())
+ HolographicOutlineHelper.getInstance(mView.getContext())
.applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
new file mode 100644
index 0000000..33d979c
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.util;
+
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * A wrapper around {@link ContentValues} with some utility methods.
+ */
+public class ContentWriter {
+
+ private final ContentValues mValues;
+ private final Context mContext;
+
+ private Bitmap mIcon;
+ private UserHandleCompat mUser;
+
+ public ContentWriter(Context context) {
+ this(new ContentValues(), context);
+ }
+
+ public ContentWriter(ContentValues values, Context context) {
+ mValues = values;
+ mContext = context;
+ }
+
+ public ContentWriter put(String key, Integer value) {
+ mValues.put(key, value);
+ return this;
+ }
+
+ public ContentWriter put(String key, Long value) {
+ mValues.put(key, value);
+ return this;
+ }
+
+ public ContentWriter put(String key, String value) {
+ mValues.put(key, value);
+ return this;
+ }
+
+ public ContentWriter put(String key, CharSequence value) {
+ mValues.put(key, value == null ? null : value.toString());
+ return this;
+ }
+
+ public ContentWriter put(String key, Intent value) {
+ mValues.put(key, value == null ? null : value.toUri(0));
+ return this;
+ }
+
+ public ContentWriter putIcon(Bitmap value, UserHandleCompat user) {
+ mIcon = value;
+ mUser = user;
+ return this;
+ }
+
+ public ContentWriter put(String key, UserHandleCompat user) {
+ return put(key, UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user));
+ }
+
+ /**
+ * Commits any pending validation and returns the final values.
+ * Must not be called on UI thread.
+ */
+ public ContentValues getValues() {
+ Preconditions.assertNonUiThread();
+ if (mIcon != null && !LauncherAppState.getInstance().getIconCache()
+ .isDefaultIcon(mIcon, mUser)) {
+ mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(mIcon));
+ mIcon = null;
+ }
+ return mValues;
+ }
+}
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
index 4fefa98..6603ee7 100644
--- a/src/com/android/launcher3/util/CursorIconInfo.java
+++ b/src/com/android/launcher3/util/CursorIconInfo.java
@@ -25,6 +25,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherIcons;
/**
* Utility class to load icon from a cursor.
@@ -59,7 +60,7 @@
info.iconResource = new ShortcutIconResource();
info.iconResource.packageName = packageName;
info.iconResource.resourceName = resourceName;
- icon = Utilities.createIconBitmap(packageName, resourceName, mContext);
+ icon = LauncherIcons.createIconBitmap(packageName, resourceName, mContext);
}
if (icon == null) {
// Failed to load from resource, try loading from DB.
@@ -72,7 +73,7 @@
* Loads the fixed bitmap from the icon if available.
*/
public Bitmap loadIcon(Cursor c) {
- return Utilities.createIconBitmap(c, iconIndex, mContext);
+ return LauncherIcons.createIconBitmap(c, iconIndex, mContext);
}
/**
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 163c953..afc45fe 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -79,8 +79,7 @@
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);
+ keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
}
public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 46e9184..8f985c3 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -18,7 +18,9 @@
import android.content.ComponentName;
+import com.android.launcher3.FolderInfo;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.compat.UserHandleCompat;
@@ -33,6 +35,46 @@
public abstract boolean matches(ItemInfo info, ComponentName cn);
+ /**
+ * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}.
+ */
+ public final HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
+ HashSet<ItemInfo> filtered = new HashSet<>();
+ for (ItemInfo i : infos) {
+ if (i instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) i;
+ ComponentName cn = info.getTargetComponent();
+ if (cn != null && matches(info, cn)) {
+ filtered.add(info);
+ }
+ } else if (i instanceof FolderInfo) {
+ FolderInfo info = (FolderInfo) i;
+ for (ShortcutInfo s : info.contents) {
+ ComponentName cn = s.getTargetComponent();
+ if (cn != null && matches(s, cn)) {
+ filtered.add(s);
+ }
+ }
+ } else if (i instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
+ ComponentName cn = info.providerName;
+ if (cn != null && matches(info, cn)) {
+ filtered.add(info);
+ }
+ }
+ }
+ return filtered;
+ }
+
+ public static ItemInfoMatcher ofUser(final UserHandleCompat user) {
+ return new ItemInfoMatcher() {
+ @Override
+ public boolean matches(ItemInfo info, ComponentName cn) {
+ return info.user.equals(user);
+ }
+ };
+ }
+
public static ItemInfoMatcher ofComponents(
final HashSet<ComponentName> components, final UserHandleCompat user) {
return new ItemInfoMatcher() {
diff --git a/src/com/android/launcher3/util/LabelComparator.java b/src/com/android/launcher3/util/LabelComparator.java
new file mode 100644
index 0000000..5da9ddf
--- /dev/null
+++ b/src/com/android/launcher3/util/LabelComparator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Extension of {@link java.text.Collator} with special handling for digits. Used for comparing
+ * user visible labels.
+ */
+public class LabelComparator implements Comparator<String> {
+
+ private final Collator mCollator = Collator.getInstance();
+
+ @Override
+ public int compare(String titleA, String titleB) {
+ // Ensure that we de-prioritize any titles that don't start with a
+ // linguistic letter or digit
+ boolean aStartsWithLetter = (titleA.length() > 0) &&
+ Character.isLetterOrDigit(titleA.codePointAt(0));
+ boolean bStartsWithLetter = (titleB.length() > 0) &&
+ Character.isLetterOrDigit(titleB.codePointAt(0));
+ if (aStartsWithLetter && !bStartsWithLetter) {
+ return -1;
+ } else if (!aStartsWithLetter && bStartsWithLetter) {
+ return 1;
+ }
+
+ // Order by the title in the current locale
+ return mCollator.compare(titleA, titleB);
+ }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 6661429..78b7a3e 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -121,7 +121,7 @@
// getting filled with the managed user apps, when it start with a fresh DB (or after
// a very long time).
if (userAppsExisted && !homescreenApps.isEmpty()) {
- mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
+ mModel.addAndBindAddedWorkspaceItems(homescreenApps);
}
}
@@ -175,7 +175,7 @@
// 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);
+ mModel.addAndBindAddedWorkspaceItems(itemList);
mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
@@ -200,7 +200,6 @@
}
}
-
/**
* Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
*/
diff --git a/src/com/android/launcher3/util/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
index 3b258e4..c8a5ffb 100644
--- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java
+++ b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
@@ -11,9 +11,6 @@
*/
public class NoLocaleSqliteContext extends ContextWrapper {
- // TODO: Use the flag defined in Context when the new SDK is available
- private static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010;
-
public NoLocaleSqliteContext(Context context) {
super(context);
}
@@ -22,6 +19,6 @@
public SQLiteDatabase openOrCreateDatabase(
String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
return super.openOrCreateDatabase(
- name, mode | MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
+ name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
}
}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 3e15d05..b61d609 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -28,15 +28,12 @@
import com.android.launcher3.Utilities;
-import java.util.ArrayList;
-
/**
* Utility methods using package manager
*/
public class PackageManagerHelper {
private static final int FLAG_SUSPENDED = 1<<30;
- private static final String LIVE_WALLPAPER_PICKER_PKG = "com.android.wallpaper.livepicker";
/**
* Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
@@ -72,7 +69,7 @@
// The value of FLAG_SUSPENDED was reused by a hidden constant
// ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
// or later.
- if (Utilities.isNycOrAbove()) {
+ if (Utilities.ATLEAST_NOUGAT) {
return (info.flags & FLAG_SUSPENDED) != 0;
} else {
return false;
@@ -80,29 +77,6 @@
}
/**
- * Returns the package for a wallpaper picker system app giving preference to a app which
- * is not as image picker.
- */
- public static String getWallpaperPickerPackage(PackageManager pm) {
- ArrayList<String> excludePackages = new ArrayList<>();
- // Exclude packages which contain an image picker
- for (ResolveInfo info : pm.queryIntentActivities(
- new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), 0)) {
- excludePackages.add(info.activityInfo.packageName);
- }
- excludePackages.add(LIVE_WALLPAPER_PICKER_PKG);
-
- for (ResolveInfo info : pm.queryIntentActivities(
- new Intent(Intent.ACTION_SET_WALLPAPER), 0)) {
- if (!excludePackages.contains(info.activityInfo.packageName) &&
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return info.activityInfo.packageName;
- }
- }
- return excludePackages.get(0);
- }
-
- /**
* Returns true if {@param srcPackage} has the permission required to start the activity from
* {@param intent}. If {@param srcPackage} is null, then the activity should not need
* any permissions
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index bade967..8eea28b 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -73,7 +73,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
ContentValues itemValues = new ContentValues();
- writeToValues(itemValues);
+ writeToValues(new ContentWriter(itemValues, null));
itemValues.writeToParcel(dest, flags);
dest.writeInt(mArg1);
diff --git a/src/com/android/launcher3/util/StringFilter.java b/src/com/android/launcher3/util/StringFilter.java
deleted file mode 100644
index f539ad1..0000000
--- a/src/com/android/launcher3/util/StringFilter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.launcher3.util;
-
-import java.util.Set;
-
-/**
- * Abstract class to filter a set of strings.
- */
-public abstract class StringFilter {
-
- private StringFilter() { }
-
- public abstract boolean matches(String str);
-
- public static StringFilter matchesAll() {
- return new StringFilter() {
- @Override
- public boolean matches(String str) {
- return true;
- }
- };
- }
-
- public static StringFilter of(final Set<String> validEntries) {
- return new StringFilter() {
- @Override
- public boolean matches(String str) {
- return validEntries.contains(str);
- }
- };
- }
-}
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
index d1409c8..3cca215 100644
--- a/src/com/android/launcher3/util/TouchController.java
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -1,8 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.view.MotionEvent;
public interface TouchController {
- boolean onTouchEvent(MotionEvent ev);
- boolean onInterceptTouchEvent(MotionEvent ev);
+
+ /**
+ * Called when the draglayer receives touch event.
+ */
+ boolean onControllerTouchEvent(MotionEvent ev);
+
+ /**
+ * Called when the draglayer receives a intercept touch event.
+ */
+ boolean onControllerInterceptTouchEvent(MotionEvent ev);
}
diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
index eaa0bb3..7c06701 100644
--- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
+++ b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
@@ -21,7 +21,7 @@
import android.graphics.Rect;
import android.view.View;
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.Workspace;
@@ -67,7 +67,7 @@
// Don't clip alpha values for the drag outline if we're using the default widget preview
boolean clipAlpha = !(mAddInfo instanceof PendingAddWidgetInfo &&
(((PendingAddWidgetInfo) mAddInfo).previewImage == 0));
- HolographicOutlineHelper.obtain(mView.getContext())
+ HolographicOutlineHelper.getInstance(mView.getContext())
.applyExpensiveOutlineWithBlur(b, canvas, clipAlpha);
canvas.setBitmap(null);
diff --git a/src/com/android/launcher3/widget/WidgetItemComparator.java b/src/com/android/launcher3/widget/WidgetItemComparator.java
new file mode 100644
index 0000000..b5aaeb9
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetItemComparator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.model.WidgetItem;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Comparator for sorting WidgetItem based on their user, title and size.
+ */
+public class WidgetItemComparator implements Comparator<WidgetItem> {
+
+ private final UserHandleCompat mMyUserHandle = UserHandleCompat.myUserHandle();
+ private final Collator mCollator = Collator.getInstance();
+
+ @Override
+ public int compare(WidgetItem a, WidgetItem b) {
+ // Independent of how the labels compare, if only one of the two widget info belongs to
+ // work profile, put that one in the back.
+ boolean thisWorkProfile = !mMyUserHandle.equals(a.user);
+ boolean otherWorkProfile = !mMyUserHandle.equals(b.user);
+ if (thisWorkProfile ^ otherWorkProfile) {
+ return thisWorkProfile ? 1 : -1;
+ }
+
+ int labelCompare = mCollator.compare(a.label, b.label);
+ if (labelCompare != 0) {
+ return labelCompare;
+ }
+
+ // If the label is same, put the smaller widget before the larger widget. If the area is
+ // also same, put the widget with smaller height before.
+ int thisArea = a.spanX * a.spanY;
+ int otherArea = b.spanX * b.spanY;
+ return thisArea == otherArea
+ ? Integer.compare(a.spanY, b.spanY)
+ : Integer.compare(thisArea, otherArea);
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
new file mode 100644
index 0000000..3e89eeb
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.ArrayList;
+
+/**
+ * Holder class to store all the information related to a single row in the widget list
+ */
+public class WidgetListRowEntry {
+
+ public final PackageItemInfo pkgItem;
+
+ public final ArrayList<WidgetItem> widgets;
+
+ /**
+ * Character that is used as a section name for the {@link ItemInfo#title}.
+ * (e.g., "G" will be stored if title is "Google")
+ */
+ public String titleSectionName;
+
+ public WidgetListRowEntry(PackageItemInfo pkgItem, ArrayList<WidgetItem> items) {
+ this.pkgItem = pkgItem;
+ this.widgets = items;
+ }
+
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 89c44c8..2e12942 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -21,7 +21,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView.State;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -29,7 +28,6 @@
import android.widget.Toast;
import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
@@ -43,13 +41,15 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.TransformingTouchDelegate;
/**
* The widgets list view container.
@@ -64,12 +64,9 @@
private DragController mDragController;
private IconCache mIconCache;
- private final Rect mTmpBgPaddingRect = new Rect();
-
/* Recycler view related member variables */
private WidgetsRecyclerView mRecyclerView;
private WidgetsListAdapter mAdapter;
- private TransformingTouchDelegate mRecyclerViewTouchDelegate;
/* Touch handling related member variables. */
private Toast mWidgetInstructionToast;
@@ -97,14 +94,8 @@
}
@Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- getRevealView().getBackground().getPadding(mTmpBgPaddingRect);
- mRecyclerViewTouchDelegate.setBounds(
- mRecyclerView.getLeft() - mTmpBgPaddingRect.left,
- mRecyclerView.getTop() - mTmpBgPaddingRect.top,
- mRecyclerView.getRight() + mTmpBgPaddingRect.right,
- mRecyclerView.getBottom() + mTmpBgPaddingRect.bottom);
+ public View getTouchDelegateTargetView() {
+ return mRecyclerView;
}
@Override
@@ -113,13 +104,6 @@
mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate);
}
//
@@ -156,7 +140,7 @@
@Override
public boolean onLongClick(View v) {
if (LOGD) {
- Log.d(TAG, String.format("onLonglick [v=%s]", v));
+ Log.d(TAG, String.format("onLongClick [v=%s]", v));
}
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
@@ -241,7 +225,7 @@
} else {
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
- preview = Utilities.createIconBitmap(icon, mLauncher);
+ preview = LauncherIcons.createIconBitmap(icon, mLauncher);
createItemInfo.spanX = createItemInfo.spanY = 1;
scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth();
}
@@ -308,23 +292,7 @@
}
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 = d.dragInfo;
- if (layout != null) {
- showOutOfSpaceMessage =
- !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
- }
- }
- if (showOutOfSpaceMessage) {
- mLauncher.showOutOfSpaceMessage(false);
- }
d.deferDragViewCleanupPostAnimation = false;
}
}
@@ -332,9 +300,8 @@
/**
* Initialize the widget data model.
*/
- public void addWidgets(WidgetsModel model) {
- mRecyclerView.setWidgets(model);
- mAdapter.setWidgetsModel(model);
+ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
+ mAdapter.setWidgets(model);
mAdapter.notifyDataSetChanged();
View loader = getContentView().findViewById(R.id.loader);
@@ -355,7 +322,7 @@
}
@Override
- public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+ public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
targetParent.containerType = LauncherLogProto.WIDGETS;
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 6b8ea49..a5846ec 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -27,16 +27,21 @@
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.util.MultiHashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.Map;
/**
* List view adapter for the widget tray.
@@ -57,7 +62,8 @@
private final View.OnClickListener mIconClickListener;
private final View.OnLongClickListener mIconLongClickListener;
- private WidgetsModel mWidgetsModel;
+ private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
+ private final AlphabeticIndexCompat mIndexer;
private final int mIndent;
@@ -67,26 +73,40 @@
mLayoutInflater = LayoutInflater.from(context);
mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
+ mIndexer = new AlphabeticIndexCompat(context);
+
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
}
- public void setWidgetsModel(WidgetsModel w) {
- mWidgetsModel = w;
+ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
+ mEntries.clear();
+ WidgetItemComparator widgetComparator = new WidgetItemComparator();
+
+ for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
+ WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+ row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
+ Collections.sort(row.widgets, widgetComparator);
+ mEntries.add(row);
+ }
+
+ Collections.sort(mEntries, new WidgetListRowEntryComparator());
}
@Override
public int getItemCount() {
- if (mWidgetsModel == null) {
- return 0;
- }
- return mWidgetsModel.getPackageSize();
+ return mEntries.size();
+ }
+
+ public String getSectionName(int pos) {
+ return mEntries.get(pos).titleSectionName;
}
@Override
public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
- List<WidgetItem> infoList = mWidgetsModel.getSortedWidgets(pos);
+ WidgetListRowEntry entry = mEntries.get(pos);
+ List<WidgetItem> infoList = entry.widgets;
ViewGroup row = holder.cellContainer;
if (DEBUG) {
@@ -121,7 +141,7 @@
}
// Bind the views in the application info section.
- holder.title.applyFromPackageItemInfo(mWidgetsModel.getPackageItemInfo(pos));
+ holder.title.applyFromPackageItemInfo(entry.pkgItem);
// Bind the view in the widget horizontal tray region.
for (int i=0; i < infoList.size(); i++) {
@@ -175,4 +195,18 @@
public long getItemId(int pos) {
return pos;
}
+
+ /**
+ * Comparator for sorting WidgetListRowEntry based on package title
+ */
+ public static class WidgetListRowEntryComparator implements Comparator<WidgetListRowEntry> {
+
+ private final LabelComparator mComparator = new LabelComparator();
+
+ @Override
+ public int compare(WidgetListRowEntry a, WidgetListRowEntry b) {
+ return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString());
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 2560661..e0a80c6 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,7 +17,6 @@
package com.android.launcher3.widget;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.support.v7.widget.LinearLayoutManager;
import android.util.AttributeSet;
@@ -32,7 +31,7 @@
public class WidgetsRecyclerView extends BaseRecyclerView {
private static final String TAG = "WidgetsRecyclerView";
- private WidgetsModel mWidgets;
+ private WidgetsListAdapter mAdapter;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -65,23 +64,10 @@
return Color.WHITE;
}
- /**
- * Sets the widget model in this view, used to determine the fast scroll position.
- */
- public void setWidgets(WidgetsModel widgets) {
- mWidgets = widgets;
- }
-
- /**
- * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
- * background bounds.
- */
@Override
- protected void dispatchDraw(Canvas canvas) {
- canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
- getWidth() - mBackgroundPadding.right,
- getHeight() - mBackgroundPadding.bottom);
- super.dispatchDraw(canvas);
+ public void setAdapter(Adapter adapter) {
+ super.setAdapter(adapter);
+ mAdapter = (WidgetsListAdapter) adapter;
}
/**
@@ -97,15 +83,14 @@
// Stop the scroller if it is scrolling
stopScroll();
- int rowCount = mWidgets.getPackageSize();
+ int rowCount = mAdapter.getItemCount();
float pos = rowCount * touchFraction;
int availableScrollHeight = getAvailableScrollHeight();
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
- PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
- return p.titleSectionName;
+ return mAdapter.getSectionName(posInt);
}
/**
@@ -121,7 +106,7 @@
// Skip early if, there no child laid out in the container.
int scrollY = getCurrentScrollY();
if (scrollY < 0) {
- mScrollbar.setThumbOffset(-1, -1);
+ mScrollbar.setThumbOffsetY(-1);
return;
}
@@ -150,13 +135,13 @@
@Override
protected int getAvailableScrollHeight() {
View child = getChildAt(0);
- int height = child.getMeasuredHeight() * mWidgets.getPackageSize();
+ int height = child.getMeasuredHeight() * mAdapter.getItemCount();
int totalHeight = getPaddingTop() + height + getPaddingBottom();
- int availableScrollHeight = totalHeight - getVisibleHeight();
+ int availableScrollHeight = totalHeight - getScrollbarTrackHeight();
return availableScrollHeight;
}
private boolean isModelNotReady() {
- return mWidgets == null || mWidgets.getPackageSize() == 0;
+ return mAdapter.getItemCount() == 0;
}
}
\ No newline at end of file
diff --git a/tests/Android.mk b/tests/Android.mk
index 61ee220..5103ced 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -17,11 +17,12 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/res/raw/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
new file mode 100644
index 0000000..9095476
--- /dev/null
+++ b/tests/res/raw/cache_data_updated_task_data.txt
@@ -0,0 +1,28 @@
+# Model data used by CacheDataUpdatedTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Auto install app shortcut
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+
+# Custom shortcuts
+bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
+bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Restored custom shortcut
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
+bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
+
+allApps componentName=app1/class1
+allApps componentName=app1/class2
+allApps componentName=app2/class1
+allApps componentName=app2/class2
\ No newline at end of file
diff --git a/tests/res/raw/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
new file mode 100644
index 0000000..84f9c16
--- /dev/null
+++ b/tests/res/raw/package_install_state_change_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by PackageInstallStateChangeTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+classMap w com.android.launcher3.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java
index 5c5069f..6be2522 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/BindWidgetTest.java
@@ -17,6 +17,7 @@
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -218,21 +219,20 @@
mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
// Insert the item
- v = new ContentValues();
+ ContentWriter writer = new ContentWriter(mTargetContext);
item.id = LauncherSettings.Settings.call(
mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
item.screenId = screenId;
- item.onAddToDatabase(mTargetContext, v);
- v.put(LauncherSettings.Favorites._ID, item.id);
- mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, item.id);
+ mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
// Reset loader
try {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- LauncherClings.markFirstRunClingDismissed(mTargetContext);
ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 0000000..ecb3782
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,190 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private final ComponentName mComponent1 = new ComponentName("a", "b");
+ private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+ private ArrayList<Long> existingScreens;
+ private ArrayList<Long> newScreens;
+ private LongArrayMap<GridOccupancy> screenOccupancy;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ existingScreens = new ArrayList<>();
+ screenOccupancy = new LongArrayMap<>();
+ newScreens = new ArrayList<>();
+
+ idp.numColumns = 5;
+ idp.numRows = 5;
+ }
+
+ private <T extends ItemInfo> AddWorkspaceItemsTask newTask(T... items) {
+ return new AddWorkspaceItemsTask(new ArrayList<>(Arrays.asList(items))) {
+
+ @Override
+ protected void addItemToDatabase(Context context, ItemInfo item,
+ long screenId, int[] pos) {
+ item.screenId = screenId;
+ item.cellX = pos[0];
+ item.cellY = pos[1];
+ }
+
+ @Override
+ protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
+ };
+ }
+
+ public void testFindSpaceForItem_prefers_second() {
+ // First screen has only one hole of size 1
+ int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
+
+ // Find a larger space
+ spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
+ }
+
+ public void testFindSpaceForItem_adds_new_screen() throws Exception {
+ // First screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+ assertFalse(oldScreens.contains(spaceFound.first));
+ assertTrue(newScreens.contains(spaceFound.first));
+ }
+
+ public void testAddItem_existing_item_ignored() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ // Nothing was added
+ assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+ }
+
+ public void testAddItem_some_items_added() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ ShortcutInfo info2 = new ShortcutInfo();
+ info2.intent = new Intent().setComponent(mComponent2);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ executeTaskForTest(newTask(info, info2)).get(0).run();
+ ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+ ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+ // only info2 should be added because info was already added to the workspace
+ // in setupWorkspaceWithHoles()
+ verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
+ animated.capture(), any(ArrayList.class));
+ assertTrue(notAnimated.getValue().isEmpty());
+
+ assertEquals(1, animated.getValue().size());
+ assertTrue(animated.getValue().contains(info2));
+ }
+
+ private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
+ occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+ for (Rect r : holes) {
+ occupancy.markCells(r, false);
+ }
+
+ existingScreens.add(screenId);
+ screenOccupancy.append(screenId, occupancy);
+
+ for (int x = 0; x < idp.numColumns; x++) {
+ for (int y = 0; y < idp.numRows; y++) {
+ if (!occupancy.cells[x][y]) {
+ continue;
+ }
+
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+ info.id = startId++;
+ info.screenId = screenId;
+ info.cellX = x;
+ info.cellY = y;
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ bgDataModel.addItem(info, false);
+ }
+ }
+ return startId;
+ }
+
+ private void commitScreensToDb() throws Exception {
+ LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+ Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ // Clear the table
+ ops.add(ContentProviderOperation.newDelete(uri).build());
+ int count = existingScreens.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues v = new ContentValues();
+ long screenId = existingScreens.get(i);
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+ }
+ getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops);
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
new file mode 100644
index 0000000..5628e82
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,208 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.support.test.InstrumentationRegistry;
+import android.test.ProviderTestCase2;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class for writing tests for Model update tasks.
+ */
+public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+
+ public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
+
+ public Context targetContext;
+ public UserHandleCompat myUser;
+
+ public InvariantDeviceProfile idp;
+ public LauncherAppState appState;
+ public MyIconCache iconCache;
+
+ public BgDataModel bgDataModel;
+ public AllAppsList allAppsList;
+ public Callbacks callbacks;
+
+ public BaseModelUpdateTaskTestCase() {
+ super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ callbacks = mock(Callbacks.class);
+ appState = mock(LauncherAppState.class);
+ myUser = UserHandleCompat.myUserHandle();
+
+ bgDataModel = new BgDataModel();
+ targetContext = InstrumentationRegistry.getTargetContext();
+ idp = new InvariantDeviceProfile();
+ iconCache = new MyIconCache(targetContext, idp);
+
+ allAppsList = new AllAppsList(iconCache, null);
+
+ when(appState.getIconCache()).thenReturn(iconCache);
+ when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+ }
+
+ /**
+ * Synchronously executes the task and returns all the UI callbacks posted.
+ */
+ public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
+ LauncherModel mockModel = mock(LauncherModel.class);
+ when(mockModel.getCallback()).thenReturn(callbacks);
+
+ Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
+ f.setAccessible(true);
+ f.set(task, mockModel);
+
+ DeferredHandler mockHandler = mock(DeferredHandler.class);
+ f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
+ f.setAccessible(true);
+ f.set(task, mockHandler);
+
+ task.execute(appState, bgDataModel, allAppsList);
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mockHandler, atLeast(0)).post(captor.capture());
+
+ return captor.getAllValues();
+ }
+
+ /**
+ * Initializes mock data for the test.
+ */
+ public void initializeData(String resourceName) throws Exception {
+ Context myContext = InstrumentationRegistry.getContext();
+ Resources res = myContext.getResources();
+ int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
+ String line;
+ HashMap<String, Class> classMap = new HashMap<>();
+ while((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+ String[] commands = line.split(" ");
+ switch (commands[0]) {
+ case "classMap":
+ classMap.put(commands[1], Class.forName(commands[2]));
+ break;
+ case "bgItem":
+ bgDataModel.addItem(
+ (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
+ break;
+ case "allApps":
+ allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1));
+ break;
+ }
+ }
+ }
+ }
+
+ private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
+ HashMap<String, Field> cache = fieldCache.get(clazz);
+ if (cache == null) {
+ cache = new HashMap<>();
+ Class c = clazz;
+ while (c != null) {
+ for (Field f : c.getDeclaredFields()) {
+ f.setAccessible(true);
+ cache.put(f.getName(), f);
+ }
+ c = c.getSuperclass();
+ }
+ fieldCache.put(clazz, cache);
+ }
+
+ Object item = clazz.newInstance();
+ for (int i = startIndex; i < fieldDef.length; i++) {
+ String[] fieldData = fieldDef[i].split("=", 2);
+ Field f = cache.get(fieldData[0]);
+ Class type = f.getType();
+ if (type == int.class || type == long.class) {
+ f.set(item, Integer.parseInt(fieldData[1]));
+ } else if (type == CharSequence.class || type == String.class) {
+ f.set(item, fieldData[1]);
+ } else if (type == Intent.class) {
+ if (!fieldData[1].startsWith("#Intent")) {
+ fieldData[1] = "#Intent;" + fieldData[1] + ";end";
+ }
+ f.set(item, Intent.parseUri(fieldData[1], 0));
+ } else if (type == ComponentName.class) {
+ f.set(item, ComponentName.unflattenFromString(fieldData[1]));
+ } else {
+ throw new Exception("Added parsing logic for "
+ + f.getName() + " of type " + f.getType());
+ }
+ }
+ return item;
+ }
+
+ public static class MyIconCache extends IconCache {
+
+ private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
+
+ public MyIconCache(Context context, InvariantDeviceProfile idp) {
+ super(context, idp);
+ }
+
+ @Override
+ protected CacheEntry cacheLocked(ComponentName componentName,
+ LauncherActivityInfoCompat info, UserHandleCompat user,
+ boolean usePackageIcon, boolean useLowResIcon) {
+ CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
+ if (entry == null) {
+ entry = new CacheEntry();
+ entry.icon = getDefaultIcon(user);
+ }
+ return entry;
+ }
+
+ public void addCache(ComponentName key, String title) {
+ CacheEntry entry = new CacheEntry();
+ entry.icon = newIcon();
+ entry.title = title;
+ mCache.put(new ComponentKey(key, UserHandleCompat.myUserHandle()), entry);
+ }
+
+ public Bitmap newIcon() {
+ return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
new file mode 100644
index 0000000..25b8df9
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,81 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link CacheDataUpdatedTask}
+ */
+public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private static final String NEW_LABEL_PREFIX = "new-label-";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ initializeData("cache_data_updated_task_data");
+ // Add dummy entries in the cache to simulate update
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
+ }
+ }
+
+ private CacheDataUpdatedTask newTask(int op, String... pkg) {
+ return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
+ }
+
+ public void testCacheUpdate_update_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+
+ // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
+ // is not updated
+ verifyUpdate(1L, 2L);
+
+ // Verify that only app1 var updated in allAppsList
+ assertFalse(allAppsList.data.isEmpty());
+ for (AppInfo info : allAppsList.data) {
+ if (info.componentName.getPackageName().equals("app1")) {
+ assertNotNull(info.iconBitmap);
+ } else {
+ assertNull(info.iconBitmap);
+ }
+ }
+ }
+
+ public void testSessionUpdate_ignores_normal_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+
+ // app1 has no restored shortcuts. Verify that nothing was updated.
+ verifyUpdate();
+ }
+
+ public void testSessionUpdate_updates_pending_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
+
+ // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
+ // were updated
+ verifyUpdate(5L, 6L);
+ }
+
+ private void verifyUpdate(Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ IconCache noOpIconCache = mock(IconCache.class);
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (updates.contains(info.id)) {
+ assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNotNull(((ShortcutInfo) info).getIcon(noOpIconCache));
+ } else {
+ assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNull(((ShortcutInfo) info).getIcon(noOpIconCache));
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
new file mode 100644
index 0000000..d655562
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -0,0 +1,61 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link PackageInstallStateChangedTask}
+ */
+public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initializeData("package_install_state_change_task_data");
+ }
+
+ private PackageInstallStateChangedTask newTask(String pkg, int progress) {
+ PackageInstallInfo installInfo = new PackageInstallInfo(pkg);
+ installInfo.progress = progress;
+ installInfo.state = PackageInstallerCompat.STATUS_INSTALLING;
+ return new PackageInstallStateChangedTask(installInfo);
+ }
+
+ public void testSessionUpdate_ignore_installed() throws Exception {
+ executeTaskForTest(newTask("app1", 30));
+
+ // No shortcuts were updated
+ verifyProgressUpdate(0);
+ }
+
+ public void testSessionUpdate_shortcuts_updated() throws Exception {
+ executeTaskForTest(newTask("app3", 30));
+
+ verifyProgressUpdate(30, 5L, 6L, 7L);
+ }
+
+ public void testSessionUpdate_widgets_updated() throws Exception {
+ executeTaskForTest(newTask("app4", 30));
+
+ verifyProgressUpdate(30, 8L, 9L);
+ }
+
+ private void verifyProgressUpdate(int progress, Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ assertEquals(updates.contains(info.id) ? progress: 0,
+ ((ShortcutInfo) info).getInstallProgress());
+ } else {
+ assertEquals(updates.contains(info.id) ? progress: -1,
+ ((LauncherAppWidgetInfo) info).installProgress);
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/AddWidgetTest.java
index a0ca60c..d536af2 100644
--- a/tests/src/com/android/launcher3/ui/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/AddWidgetTest.java
@@ -5,7 +5,6 @@
import android.test.suitebuilder.annotation.LargeTest;
import android.view.View;
-import com.android.launcher3.CellLayout;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetInfo;
@@ -51,7 +50,7 @@
// Drag widget to homescreen
UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
.hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
- dragToWorkspace(widget);
+ dragToWorkspace(widget, false);
assertNotNull(launcher.getWorkspace().getFirstMatch(new ItemOperator() {
@Override
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 56fc90a..f45710c 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -47,7 +47,7 @@
// Drag icon to homescreen.
UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
- dragToWorkspace(icon);
+ dragToWorkspace(icon, true);
// Verify that the icon works on homescreen.
mDevice.findObject(By.text(mSettingsApp.getLabel().toString())).click();
diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
index e858d17..fcf7122 100644
--- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
+++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
@@ -23,7 +23,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherClings;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -141,29 +140,52 @@
/**
* Drags an icon to the center of homescreen.
*/
- protected void dragToWorkspace(UiObject2 icon) {
+ protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
Point center = icon.getVisibleCenter();
// Action Down
sendPointer(MotionEvent.ACTION_DOWN, center);
- // Wait until "Remove/Delete target is visible
+ UiObject2 dragLayer = findViewById(R.id.drag_layer);
+
+ if (expectedToShowShortcuts) {
+ // Make sure shortcuts show up, and then move a bit to hide them.
+ assertNotNull(findViewById(R.id.deep_shortcuts_container));
+
+ Point moveLocation = new Point(center);
+ int distanceToMove = mTargetContext.getResources().getDimensionPixelSize(
+ R.dimen.deep_shortcuts_start_drag_threshold) + 50;
+ if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
+ moveLocation.y -= distanceToMove;
+ } else {
+ moveLocation.y += distanceToMove;
+ }
+ movePointer(center, moveLocation);
+
+ assertNull(findViewById(R.id.deep_shortcuts_container));
+ }
+
+ // Wait until Remove/Delete target is visible
assertNotNull(findViewById(R.id.delete_target_text));
- Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter();
+ Point moveLocation = dragLayer.getVisibleCenter();
// Move to center
- while(!moveLocation.equals(center)) {
- center.x = getNextMoveValue(moveLocation.x, center.x);
- center.y = getNextMoveValue(moveLocation.y, center.y);
- sendPointer(MotionEvent.ACTION_MOVE, center);
- }
+ movePointer(center, moveLocation);
sendPointer(MotionEvent.ACTION_UP, center);
// Wait until remove target is gone.
mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT);
}
+ private void movePointer(Point from, Point to) {
+ while(!from.equals(to)) {
+ from.x = getNextMoveValue(to.x, from.x);
+ from.y = getNextMoveValue(to.y, from.y);
+ sendPointer(MotionEvent.ACTION_MOVE, from);
+ }
+ }
+
private int getNextMoveValue(int targetValue, int oldValue) {
if (targetValue - oldValue > 10) {
return oldValue + 10;
@@ -174,7 +196,7 @@
}
}
- private void sendPointer(int action, Point point) {
+ protected void sendPointer(int action, Point point) {
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), action, point.x, point.y, 0);
getInstrumentation().sendPointerSync(event);
@@ -189,7 +211,6 @@
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
- LauncherClings.markFirstRunClingDismissed(mTargetContext);
ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
runTestOnUiThread(new Runnable() {
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
new file mode 100644
index 0000000..f892e63
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -0,0 +1,68 @@
+package com.android.launcher3.ui;
+
+import android.graphics.Point;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+
+/**
+ * Test for verifying that shortcuts are shown and can be launched after long pressing an app
+ */
+@LargeTest
+public class ShortcutsLaunchTest extends LauncherInstrumentationTestCase {
+
+ private LauncherActivityInfoCompat mSettingsApp;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+ .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+ }
+
+ public void testAppLauncher_portrait() throws Exception {
+ lockRotation(true);
+ performTest();
+ }
+
+ public void testAppLauncher_landscape() throws Exception {
+ lockRotation(false);
+ performTest();
+ }
+
+ private void performTest() throws Exception {
+ startLauncher();
+
+ // Open all apps and wait for load complete
+ final UiObject2 appsContainer = openAllApps();
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Find settings app and verify shortcuts appear when long pressed
+ UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
+ // Press icon center until shortcuts appear
+ Point iconCenter = icon.getVisibleCenter();
+ sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+ UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container);
+ assertNotNull(deepShortcutsContainer);
+ sendPointer(MotionEvent.ACTION_UP, iconCenter);
+
+ // Verify that launching a shortcut opens a page with the same text
+ assertTrue(deepShortcutsContainer.getChildCount() > 0);
+ UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+ .findObject(getSelectorForId(R.id.deep_shortcut));
+ shortcut.click();
+ assertTrue(mDevice.wait(Until.hasObject(By.pkg(
+ mSettingsApp.getComponentName().getPackageName())
+ .text(shortcut.getText())), DEFAULT_UI_TIMEOUT));
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
new file mode 100644
index 0000000..f9a0e6e
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -0,0 +1,74 @@
+package com.android.launcher3.ui;
+
+import android.graphics.Point;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+
+/**
+ * Test for dragging a deep shortcut to the home screen.
+ */
+@LargeTest
+public class ShortcutsToHomeTest extends LauncherInstrumentationTestCase {
+
+ private LauncherActivityInfoCompat mSettingsApp;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+ .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+ }
+
+ public void testDragIcon_portrait() throws Throwable {
+ lockRotation(true);
+ performTest();
+ }
+
+ public void testDragIcon_landscape() throws Throwable {
+ lockRotation(false);
+ performTest();
+ }
+
+ private void performTest() throws Throwable {
+ clearHomescreen();
+ startLauncher();
+
+ // Open all apps and wait for load complete.
+ final UiObject2 appsContainer = openAllApps();
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+ // Find the app and long press it to show shortcuts.
+ UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
+ // Press icon center until shortcuts appear
+ Point iconCenter = icon.getVisibleCenter();
+ sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+ UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container);
+ assertNotNull(deepShortcutsContainer);
+ sendPointer(MotionEvent.ACTION_UP, iconCenter);
+
+ // Drag the first shortcut to the home screen.
+ assertTrue(deepShortcutsContainer.getChildCount() > 0);
+ UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+ .findObject(getSelectorForId(R.id.deep_shortcut));
+ String shortcutName = shortcut.getText();
+ dragToWorkspace(shortcut, false);
+
+ // Verify that the shortcut works on home screen
+ // (the app opens and has the same text as the shortcut).
+ mDevice.findObject(By.text(shortcutName)).click();
+ assertTrue(mDevice.wait(Until.hasObject(By.pkg(
+ mSettingsApp.getComponentName().getPackageName())
+ .text(shortcutName)), DEFAULT_UI_TIMEOUT));
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index eee567f..79aed80 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -49,8 +49,6 @@
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() {