diff options
207 files changed, 3432 insertions, 3853 deletions
diff --git a/build.gradle b/build.gradle index e103d792f..0c00da9d3 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { 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' } } diff --git a/res/drawable-hdpi/quantum_panel_bitmap.9.png b/res/drawable-hdpi/quantum_panel_bitmap.9.png Binary files differdeleted file mode 100644 index d2aee737f..000000000 --- a/res/drawable-hdpi/quantum_panel_bitmap.9.png +++ /dev/null diff --git a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png Binary files differdeleted file mode 100644 index 78345b82a..000000000 --- a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png +++ /dev/null diff --git a/res/drawable-mdpi/quantum_panel_bitmap.9.png b/res/drawable-mdpi/quantum_panel_bitmap.9.png Binary files differdeleted file mode 100644 index 9325d491f..000000000 --- a/res/drawable-mdpi/quantum_panel_bitmap.9.png +++ /dev/null diff --git a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png Binary files differdeleted file mode 100644 index bf74fa060..000000000 --- a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png +++ /dev/null diff --git a/res/drawable-v21/quantum_panel.xml b/res/drawable-v21/quantum_panel.xml deleted file mode 100644 index d1c078335..000000000 --- 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 405ad5173..000000000 --- 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/quantum_panel_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_bitmap.9.png Binary files differdeleted file mode 100644 index b89e8b410..000000000 --- a/res/drawable-xhdpi/quantum_panel_bitmap.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png Binary files differdeleted file mode 100644 index 1d1713603..000000000 --- a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_bitmap.9.png Binary files differdeleted file mode 100644 index 1dd1f6db8..000000000 --- a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png Binary files differdeleted file mode 100644 index 48d584b95..000000000 --- a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png Binary files differdeleted file mode 100644 index 915177d78..000000000 --- a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png Binary files differdeleted file mode 100644 index 27b846652..000000000 --- a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png +++ /dev/null diff --git a/res/drawable/quantum_panel.xml b/res/drawable/quantum_panel.xml index 1f4fb711b..fda100370 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 6642e78ac..b113b376f 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 a2e2f9bba..dd981dd20 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 12c01b77d..06cb55040 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 4909eb38b..803a1b561 100644 --- a/res/layout/all_apps.xml +++ b/res/layout/all_apps.xml @@ -46,21 +46,29 @@ <!-- 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: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 +76,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 000000000..91a1e45a0 --- /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/qsb_blocker_view.xml b/res/layout/qsb_blocker_view.xml index 58a148eb7..453eebe4f 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 b75e3b541..6fa843d63 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 c4431be25..d193a5ef4 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 9152ea54b..dd427e3fb 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 c20dc1235..02d5cc1c6 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 c80d162c5..437e9743a 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 1a4f31b07..fe0ef4f8d 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 a9344f9ad..bd7b874d4 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 f5dba2dae..b15dc6014 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 2c6d3d472..13d164175 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 d5108e9f8..60cdd474e 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 579b95a69..705ca040e 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 4ba049821..c12ec8d80 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 373920cfc..8eacd0a60 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 93041408d..220bd4b45 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 e461d46f0..990c901be 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 119232dab..c82b7ee29 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 44daaa84f..baae4b002 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 44daaa84f..baae4b002 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 44daaa84f..baae4b002 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 4bd6c8854..250f78483 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 a4fc62038..1fdf3ef8d 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 feb63ca31..a0ee1e93f 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 76ef5c689..b2aed2e05 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 95675a7eb..88a4fb5cf 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 40e6d96ba..0aa01b90e 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 01d6b272d..e261b7403 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 f42748ccb..591dff7a8 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 c03ecdc51..766cddb19 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 39a176c48..c14816af5 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 f534e1e6d..3d1fecb5a 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 b4fb50eab..ce370f9e1 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 279749bac..c2003b660 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 c7401a991..78848955f 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 00e8d6390..16c375298 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 7586ae37e..f39628d1d 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 b82122237..e4e1242bc 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 ffea51f1d..c04bf129b 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 8729abad7..ea9d381a1 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 1a200d710..7c413897d 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 009fa4af0..7762cd711 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 864c97925..862a65279 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 2c505673e..55fe36ca5 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 1ef438f29..bcc1699a9 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 71e02d00a..75700cece 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 e2311024f..31b7db44c 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 13ebaa3b1..1564d459a 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 924b399a6..6458d0b4a 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 7e44707fc..83da8a1b7 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 941d1acb4..95a558aae 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 45e185649..d9efe5d47 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 1ec443cc0..5938b8e53 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 215315ad1..63de9cb37 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 bc4e484ce..c65f3abef 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 449094de2..38f027a63 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 8accfcc72..330352f40 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 0134ae1c4..58f63e6ad 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 3be9becfc..38769aaf5 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 feb2a36c5..1e2fc47d0 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 6d684fa85..000c7adb2 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 82a756f39..9969997de 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 5905bcfff..6faf57f78 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 ad30c977d..7bc18194a 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 1e0ed28b8..df4b41115 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 7210d79a8..d0a2e5925 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 b55cb0d0a..0682f8d16 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 c4fbaf3e8..701eaf8d3 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 67a63cff6..a396f18b2 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 dfcc1b03b..907de7932 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 03c8f064f..20654e057 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 283808805..ead666cf7 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 7db0ae465..fd51b7135 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 4e8b86fd8..ba19ddca9 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 90dec7226..4bcda7afe 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 b944ebcd5..55e02e9a0 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 e95ee2db2..f30b55987 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 1f73f0423..750b6a0e8 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 e4c637d8c..b285de0d3 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 0e541d37a..77b7367ca 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 2bf5ad6f9..2d953e68a 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 e3ed354c2..796781262 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 ad0f99a01..236fc4246 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 24f391d71..8d4c74497 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 10fbef4e4..88d429543 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/config.xml b/res/values/config.xml index a942f0211..5b3ee4633 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 eff9d21e6..316e748bb 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -54,6 +54,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 +62,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 +163,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> @@ -183,6 +181,9 @@ also happens to equal 19dp--> <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen> +<!-- Touch handling --> + <dimen name="edge_of_screen_threshold">8dp</dimen> + <!-- Other --> <!-- Approximates the system status bar height. Not guaranteed to be always be correct. --> <dimen name="status_bar_height">24dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index 60a37e5ef..a9c970d6d 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 cd06b7560..90338ae44 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> diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java new file mode 100644 index 000000000..65da00211 --- /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 c4315936c..0e465a41e 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -23,7 +23,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; 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 @@ import java.util.List; /** * 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 @@ class AllAppsList { 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 @@ class AllAppsList { } /** - * 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); } diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java index 0e188743f..7074f7838 100644 --- a/src/com/android/launcher3/AnotherWindowDropTarget.java +++ b/src/com/android/launcher3/AnotherWindowDropTarget.java @@ -27,7 +27,7 @@ import android.graphics.Rect; 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 4c4d67c59..3b22f46f2 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -52,11 +52,6 @@ public class AppInfo extends ItemInfo { 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 @@ public class AppInfo extends ItemInfo { 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 @@ public class AppInfo extends ItemInfo { 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 cd27b4c58..d00d5dda2 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.Context; 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 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe // 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 View[] mDragHandles = new View[HANDLE_COUNT]; - private final ImageView mLeftHandle; - private final ImageView mRightHandle; - private final ImageView mTopHandle; - private final ImageView mBottomHandle; + private LauncherAppWidgetHostView mWidgetView; + private CellLayout mCellLayout; + private DragLayer mDragLayer; - private final Rect mWidgetPadding; + private Rect mWidgetPadding; private final int mBackgroundPadding; private final int mTouchTargetWidth; @@ -51,17 +57,20 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe 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 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe private int mTopTouchRegionAdjustment = 0; private int mBottomTouchRegionAdjustment = 0; - public AppWidgetResizeFrame(Context context, - LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { + private int mXDown, mYDown; + + 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); - super(context); 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 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe 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 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe 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); } - return anyBordersActive; - } - /** - * Here we bound the deltas such that the frame cannot be stretched beyond the extents - * of the CellLayout, and such that the frame's borders can't cross. - */ - public void updateDeltas(int deltaX, int deltaY) { if (mLeftBorderActive) { - mDeltaX = Math.max(-mBaselineX, deltaX); - mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX); + mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth); } else if (mRightBorderActive) { - mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX); - mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX); + mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight()); + } else { + mDeltaXRange.set(0, 0); } + mBaselineX.set(getLeft(), getRight()); if (mTopBorderActive) { - mDeltaY = Math.max(-mBaselineY, deltaY); - mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY); + mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth); } else if (mBottomBorderActive) { - mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY); - mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY); + mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom()); + } else { + mDeltaYRange.set(0, 0); } - } + mBaselineY.set(getTop(), getBottom()); - public void visualizeResizeForDelta(int deltaX, int deltaY) { - visualizeResizeForDelta(deltaX, deltaY, false); + return anyBordersActive; } /** - * Based on the deltas, we resize the frame, and, if needed, we resize the widget. + * Based on the deltas, we resize the frame. */ - private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) { - updateDeltas(deltaX, deltaY); - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + public void visualizeResizeForDelta(int deltaX, int deltaY) { + mDeltaX = mDeltaXRange.clamp(deltaX); + mDeltaY = mDeltaYRange.clamp(deltaY); - if (mLeftBorderActive) { - lp.x = mBaselineX + mDeltaX; - lp.width = mBaselineWidth - mDeltaX; - } else if (mRightBorderActive) { - lp.width = mBaselineWidth + mDeltaX; - } + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + mDeltaX = mDeltaXRange.clamp(deltaX); + mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1); + lp.x = mTempRange1.start; + lp.width = mTempRange1.size(); - if (mTopBorderActive) { - lp.y = mBaselineY + mDeltaY; - lp.height = mBaselineHeight - mDeltaY; - } else if (mBottomBorderActive) { - lp.height = mBaselineHeight + mDeltaY; - } + mDeltaY = mDeltaYRange.clamp(deltaY); + mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1); + lp.y = mTempRange1.start; + lp.height = mTempRange1.size(); - resizeWidgetIfNeeded(onDismiss); + resizeWidgetIfNeeded(false); 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(); - - int deltaX = mDeltaX + mDeltaXAddOn; - int deltaY = mDeltaY + mDeltaYAddOn; + float xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); + float yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); - 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 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX; int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY; - int hSpanDelta = 0; - int vSpanDelta = 0; - // For each border, we bound the resizing based on the minimum width, and the maximum // expandability. - if (mLeftBorderActive) { - cellXInc = Math.max(-cellX, hSpanInc); - cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc); - hSpanInc *= -1; - hSpanInc = Math.min(cellX, hSpanInc); - hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); - hSpanDelta = -hSpanInc; - - } else if (mRightBorderActive) { - hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc); - hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); - hSpanDelta = hSpanInc; - } - - if (mTopBorderActive) { - cellYInc = Math.max(-cellY, vSpanInc); - cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc); - vSpanInc *= -1; - vSpanInc = Math.min(cellY, vSpanInc); - vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); - vSpanDelta = -vSpanInc; - } else if (mBottomBorderActive) { - vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc); - vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); - vSpanDelta = vSpanInc; + 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; } - 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,7 +336,7 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe requestLayout(); } - public void onTouchUp() { + private void onTouchUp() { int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); @@ -450,10 +388,9 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe 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 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe 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 class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe 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 d5309b4f0..8b5a8a863 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -38,6 +38,7 @@ import android.util.Patterns; 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 +437,7 @@ public class AutoInstallsLayout { return -1; } - ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext)); + ItemInfo.writeBitmap(mValues, 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 96942ee6f..b7321d94f 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -16,17 +16,24 @@ package com.android.launcher3; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Point; +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. @@ -39,12 +46,16 @@ public abstract class BaseContainerView extends FrameLayout protected int mContainerPaddingTop; protected int mContainerPaddingBottom; - private InsetDrawable mRevealDrawable; protected final Drawable mBaseDrawable; + private final Rect mBgPaddingRect = new Rect(); 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 +83,12 @@ public abstract class BaseContainerView extends FrameLayout 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 @@ -93,10 +110,36 @@ public abstract class BaseContainerView extends FrameLayout } @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + getRevealView().getBackground().getPadding(mBgPaddingRect); + + View touchDelegateTargetView = getTouchDelegateTargetView(); + if (touchDelegateTargetView != null) { + mTouchDelegate.setBounds( + touchDelegateTargetView.getLeft() - mBgPaddingRect.left, + touchDelegateTargetView.getTop() - mBgPaddingRect.top, + touchDelegateTargetView.getRight() + mBgPaddingRect.right, + touchDelegateTargetView.getBottom() + mBgPaddingRect.bottom); + } + } + + @Override public void onLauncherLayoutChanged() { updatePaddings(); } + @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); } @@ -130,14 +173,47 @@ public abstract class BaseContainerView extends FrameLayout } } - mRevealDrawable = new InsetDrawable(mBaseDrawable, + InsetDrawable revealDrawable = 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); + mRevealView.setBackground(revealDrawable); + mContent.setBackground(revealDrawable); + } + + /** + * 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; } + return false; } + + public abstract View getTouchDelegateTargetView(); } diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 45bc94006..6fdf45450 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -18,10 +18,11 @@ package com.android.launcher3; 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 @@ public abstract class BaseRecyclerView extends RecyclerView @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 @@ public abstract class BaseRecyclerView extends RecyclerView 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 @@ public abstract class BaseRecyclerView extends RecyclerView 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 @@ public abstract class BaseRecyclerView extends RecyclerView * 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 @@ public abstract class BaseRecyclerView extends RecyclerView } /** - * 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 @@ public abstract class BaseRecyclerView extends RecyclerView 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 3d71632ce..40c5ed65d 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 @@ public class BaseRecyclerViewFastScrollBar { 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; + + private final Rect mTmpRect = new Rect(); + private final BaseRecyclerView mRv; + + private final boolean mIsRtl; - @Thunk BaseRecyclerView mRv; - private BaseRecyclerViewFastScrollPopup mPopup; - - private AnimatorSet mScrollbarAnimator; - - 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 @@ public class BaseRecyclerViewFastScrollBar { // 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 @@ public class BaseRecyclerViewFastScrollBar { 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); - updateThumbPath(); - mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y, - mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight); - mRv.invalidate(mInvalidateRect); - } - public Point getThumbOffset() { - return mThumbOffset; + // 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); } - // 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 getThumbOffsetY() { + return mThumbOffsetY; } - public int getThumbWidth() { - return mThumbWidth; - } + 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()); - // 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; + mWidth = width; updateThumbPath(); - mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth, - mRv.getVisibleHeight()); - mRv.invalidate(mInvalidateRect); } - public int getTrackWidth() { - return mTrackWidth; + /** + * Updates the path for the thumb drawable. + */ + private void updateThumbPath() { + int smallWidth = mIsRtl ? mWidth : -mWidth; + int largeWidth = mIsRtl ? mMaxWidth : -mMaxWidth; + + 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 @@ public class BaseRecyclerViewFastScrollBar { 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 @@ public class BaseRecyclerViewFastScrollBar { 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 class BaseRecyclerViewFastScrollBar { } 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 b9e627775..000000000 --- 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 a294fa538..7d693ec2b 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -42,6 +42,7 @@ import android.widget.TextView; 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; @@ -150,7 +151,7 @@ public class BubbleTextView extends TextView mLongPressHelper = new CheckLongPressHelper(this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); - mOutlineHelper = HolographicOutlineHelper.obtain(getContext()); + mOutlineHelper = HolographicOutlineHelper.getInstance(getContext()); setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); } @@ -328,7 +329,7 @@ public class BubbleTextView extends TextView 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 57fd0e70a..5c7ea767c 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -1054,11 +1054,11 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { 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(); @@ -2215,10 +2215,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { 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 diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index f9f8e80ca..655c21868 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -104,7 +104,7 @@ public class DeviceProfile { public int hotseatCellWidthPx; public int hotseatCellHeightPx; public int hotseatIconSizePx; - private int hotseatBarHeightPx; + public int hotseatBarHeightPx; private int hotseatBarTopPaddingPx; private int hotseatLandGutterPx; @@ -463,7 +463,6 @@ public class DeviceProfile { 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(); diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java index efbb9d7b6..2fb495fdd 100644 --- a/src/com/android/launcher3/DragSource.java +++ b/src/com/android/launcher3/DragSource.java @@ -19,12 +19,12 @@ package com.android.launcher3; 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 efdeb1fa3..e91fc15e0 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -107,17 +107,6 @@ public interface DropTarget { /** * Handle an object being dropped on the DropTarget - * - * @param source DragSource where the drag started - * @param x X coordinate of the drop location - * @param y Y coordinate of the drop location - * @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 @@ public interface DropTarget { /** * Check if a drop action can occur at, or near, the requested location. * This will be called just before onDrop. - * - * @param source DragSource where the drag started - * @param x X coordinate of the drop location - * @param y Y coordinate of the drop location - * @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 c06f727a5..d05673ccc 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -99,4 +99,12 @@ public class ExtendedEditText extends EditText { ((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/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index 789c3f929..b36734bab 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -250,14 +250,6 @@ public class FocusHelper { } 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 @@ public class FocusHelper { } 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 @@ public class FocusHelper { } } - /** - * 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 c0a8caaa3..8b70d1c70 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -45,11 +45,6 @@ public class FolderInfo extends ItemInfo { */ public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004; - /** - * Whether this folder has been opened - */ - public boolean opened; - public int options; /** diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index 0fbbc19ab..b93c6dfa1 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; 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 @@ public class Hotseat extends FrameLayout } @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 5c86b6b33..661f99b9e 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -47,6 +47,7 @@ import com.android.launcher3.compat.LauncherAppsCompat; 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; @@ -187,7 +188,7 @@ public class IconCache { private Bitmap makeDefaultIcon(UserHandleCompat user) { Drawable unbadged = getFullResDefaultActivityIcon(); - return Utilities.createBadgedIconBitmap(unbadged, user, mContext); + return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext); } /** @@ -223,7 +224,7 @@ public class IconCache { 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 @@ public class IconCache { } } - @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 @@ public class IconCache { } 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); } /** @@ -555,7 +555,7 @@ public class IconCache { // 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 +606,7 @@ public class IconCache { entry.title = title; } if (icon != null) { - entry.icon = Utilities.createIconBitmap(icon, mContext); + entry.icon = LauncherIcons.createIconBitmap(icon, mContext); } } @@ -641,7 +641,7 @@ public class IconCache { // 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 +774,9 @@ public class IconCache { 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 +788,10 @@ public class IconCache { } 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 d8e58d829..bd20e324b 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -226,7 +226,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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; diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index c0c22a325..20437721e 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -59,7 +59,7 @@ public class ItemInfo { 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; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4672e080a..40820fa9a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -18,9 +18,7 @@ package com.android.launcher3; 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.AlertDialog; 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,12 @@ import android.content.pm.PackageManager; 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.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 +63,12 @@ import android.util.Log; 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 +76,6 @@ import android.view.accessibility.AccessibilityEvent; 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; @@ -106,10 +99,12 @@ import com.android.launcher3.dragndrop.DragView; 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 +173,6 @@ public class Launcher extends Activity 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 +203,6 @@ public class Launcher extends Activity 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 +230,8 @@ public class Launcher extends Activity // 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 +252,16 @@ public class Launcher extends Activity 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 +272,6 @@ public class Launcher extends Activity // 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 +313,9 @@ public class Launcher extends Activity private UserEventDispatcher mUserEventDispatcher; + private float mLastDispatchTouchEventX = 0.0f; + private float mEdgeOfScreenThresholdPx = 0.0f; + public ViewGroupFocusHelper mFocusHandler; private boolean mRotationEnabled = false; @@ -422,6 +386,9 @@ public class Launcher extends Activity setContentView(R.layout.launcher); + mEdgeOfScreenThresholdPx = getResources() + .getDimensionPixelSize(R.dimen.edge_of_screen_threshold); + setupViews(); mDeviceProfile.layout(this, false /* notifyListeners */); mExtractedColors = new ExtractedColors(); @@ -432,8 +399,7 @@ public class Launcher extends Activity lockAllApps(); - mSavedState = savedInstanceState; - restoreState(mSavedState); + restoreState(savedInstanceState); if (LauncherAppState.PROFILE_STARTUP) { Trace.endSection(); @@ -441,11 +407,18 @@ public class Launcher extends Activity // 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 +426,6 @@ public class Launcher extends Activity 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. @@ -479,6 +449,13 @@ public class Launcher extends Activity 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()) { @@ -582,7 +559,7 @@ public class Launcher extends Activity } @Override - public void onLauncherProviderChange() { + public void onLauncherProviderChanged() { if (mLauncherCallbacks != null) { mLauncherCallbacks.onLauncherProviderChange(); } @@ -608,7 +585,7 @@ public class Launcher extends Activity } /** - * 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() { @@ -1227,11 +1204,8 @@ public class Launcher extends Activity 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 +1234,6 @@ public class Launcher extends Activity } /** - * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type - * State - */ - private static State intToState(int stateOrdinal) { - State state = State.WORKSPACE; - final State[] stateValues = State.values(); - for (int i = 0; i < stateValues.length; i++) { - if (stateValues[i].ordinal() == stateOrdinal) { - state = stateValues[i]; - break; - } - } - return state; - } - - /** * Restores the previous state, if it exists. * * @param savedState The previous state. @@ -1285,17 +1243,14 @@ public class Launcher extends Activity 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); @@ -1355,7 +1310,7 @@ public class Launcher extends Activity } // 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 +1328,36 @@ public class Launcher extends Activity 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 +1455,7 @@ public class Launcher extends Activity } if (!foundCellSpan) { - showOutOfSpaceMessage(isHotseatLayout(layout)); + mWorkspace.onNoCellFound(layout); return; } @@ -1573,10 +1511,6 @@ public class Launcher extends Activity 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 +1518,7 @@ public class Launcher extends Activity 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 +1529,6 @@ public class Launcher extends Activity } } mIsResumeFromActionScreenOff = true; - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateAutoAdvanceState(); } } }; @@ -1611,11 +1540,9 @@ public class Launcher extends Activity // 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 +1552,10 @@ public class Launcher extends Activity @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - mVisible = false; - if (mAttached) { unregisterReceiver(mReceiver); mAttached = false; } - updateAutoAdvanceState(); if (mLauncherCallbacks != null) { mLauncherCallbacks.onDetachedFromWindow(); @@ -1639,12 +1563,10 @@ public class Launcher extends Activity } 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 +1601,6 @@ public class Launcher extends Activity } } - @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 +1649,6 @@ public class Launcher extends Activity 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 +1663,10 @@ public class Launcher extends Activity // 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 +1674,7 @@ public class Launcher extends Activity // 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 +1757,9 @@ public class Launcher extends Activity 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 +1777,6 @@ public class Launcher extends Activity public void onDestroy() { super.onDestroy(); - // Remove all pending runnables - mHandler.removeMessages(ADVANCE_MSG); - mHandler.removeMessages(0); mWorkspace.removeCallbacks(mBuildLayersRunnable); mWorkspace.removeFolderListeners(); @@ -1964,15 +1799,11 @@ public class Launcher extends Activity } mAppWidgetHost = null; - mWidgetsToAdvance.clear(); - TextKeyListener.getInstance().release(); ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE)) .removeAccessibilityStateChangeListener(this); - unregisterReceiver(mUiBroadcastReceiver); - LauncherAnimUtils.onDestroyActivity(); if (mLauncherCallbacks != null) { @@ -2155,7 +1986,7 @@ public class Launcher extends Activity protected void moveToCustomContentScreen(boolean animate) { // Close any folders that may be open. - closeFolder(); + AbstractFloatingView.closeAllOpenViews(this, animate); mWorkspace.moveToCustomContentScreen(animate); } @@ -2285,7 +2116,6 @@ public class Launcher extends Activity } else if (itemInfo instanceof LauncherAppWidgetInfo) { final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo; mWorkspace.removeWorkspaceItem(v); - removeWidgetToAutoAdvance(v); if (deleteFromDb) { deleteWidgetInfo(widgetInfo); } @@ -2347,21 +2177,19 @@ public class Launcher extends Activity 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 +2439,10 @@ public class Launcher extends Activity 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,7 +2464,7 @@ public class Launcher extends Activity * 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; } @@ -2778,6 +2606,7 @@ public class Launcher extends Activity } } + @TargetApi(Build.VERSION_CODES.M) private Bundle getActivityLaunchOptions(View v) { if (Utilities.ATLEAST_MARSHMALLOW) { int left = 0, top = 0; @@ -2854,227 +2683,10 @@ public class Launcher extends Activity 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 +2701,15 @@ public class Launcher extends Activity return true; } + boolean fromEdgeOfScreen = mLastDispatchTouchEventX < mEdgeOfScreenThresholdPx + || mLastDispatchTouchEventX > (mDeviceProfile.widthPx - mEdgeOfScreenThresholdPx); + if (v instanceof Workspace) { if (!mWorkspace.isInOverviewMode()) { - if (!mWorkspace.isTouchActive()) { + if (!mWorkspace.isTouchActive() && !fromEdgeOfScreen) { + 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 +2736,21 @@ public class Launcher extends Activity 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 (fromEdgeOfScreen) { + 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 +2758,7 @@ public class Launcher extends Activity 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 +2837,6 @@ public class Launcher extends Activity // 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() @@ -3329,12 +2941,7 @@ public class Launcher extends Activity // 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() @@ -3558,7 +3165,6 @@ public class Launcher extends Activity mWorkspace.clearDropTargets(); mWorkspace.removeAllWorkspaceScreens(); - mWidgetsToAdvance.clear(); if (mHotseat != null) { mHotseat.resetLayout(); } @@ -3806,7 +3412,7 @@ public class Launcher extends Activity 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 +3587,6 @@ public class Launcher extends Activity 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 +3837,22 @@ public class Launcher extends Activity } } - 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 +4008,6 @@ public class Launcher extends Activity */ 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 +4054,65 @@ public class Launcher extends Activity } } + @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 +4121,6 @@ public class Launcher extends Activity 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 7861a106f..1ee4a1a37 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -86,10 +86,10 @@ public class LauncherAppState { 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); diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index ed1079ff3..b3db092da 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -20,6 +20,9 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.graphics.Rect; +import android.os.Handler; +import android.os.SystemClock; +import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -28,6 +31,7 @@ import android.view.ViewConfiguration; 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; @@ -39,6 +43,13 @@ import java.util.ArrayList; */ public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener { + // 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,7 +63,9 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc @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); @@ -66,7 +79,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc @Override protected View getErrorView() { - return mInflater.inflate(mErrorViewId, this, false); + return mInflater.inflate(R.layout.appwidget_error, this, false); } public void updateLastInflationOrientation() { @@ -78,6 +91,9 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc // 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 +169,19 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc 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 +200,6 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc return info; } - public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() { - return (LauncherAppWidgetProviderInfo) getAppWidgetInfo(); - } - @Override public void onTouchComplete() { if (!mLongPressHelper.hasPerformedLongPress()) { @@ -296,4 +321,79 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc 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/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java deleted file mode 100644 index c1282b51c..000000000 --- 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 3ac9773d9..55bd0a45a 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -57,8 +57,13 @@ import com.android.launcher3.config.ProviderConfig; 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.BgDataModel; 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.WidgetsModel; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.provider.LauncherDbUtils; @@ -69,12 +74,12 @@ import com.android.launcher3.util.ComponentKey; 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; @@ -90,7 +95,6 @@ import java.util.HashSet; 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,9 +146,6 @@ public class LauncherModel extends BroadcastReceiver // 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() { @@ -159,38 +160,11 @@ public class LauncherModel extends BroadcastReceiver } }; - // 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 > @@ -226,22 +200,18 @@ public class LauncherModel extends BroadcastReceiver 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) { 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; @@ -276,7 +246,7 @@ public class LauncherModel extends BroadcastReceiver @Override public void run() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { final HashSet<ItemInfo> updates = new HashSet<>(); if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { @@ -284,7 +254,7 @@ public class LauncherModel extends BroadcastReceiver return; } - for (ItemInfo info : sBgItemsIdMap) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo) { ShortcutInfo si = (ShortcutInfo) info; ComponentName cn = si.getTargetComponent(); @@ -301,7 +271,7 @@ public class LauncherModel extends BroadcastReceiver } } - for (LauncherAppWidgetInfo widget : sBgAppWidgets) { + for (LauncherAppWidgetInfo widget : sBgDataModel.appWidgets) { if (widget.providerName.getPackageName().equals(installInfo.packageName)) { widget.installProgress = installInfo.progress; updates.add(widget); @@ -334,25 +304,17 @@ public class LauncherModel extends BroadcastReceiver @Override public void run() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { ArrayList<ShortcutInfo> updates = new ArrayList<>(); UserHandleCompat user = UserHandleCompat.myUserHandle(); - for (ItemInfo info : sBgItemsIdMap) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { 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); - } + si.updateIcon(mIconCache); updates.add(si); } } @@ -418,8 +380,8 @@ public class LauncherModel extends BroadcastReceiver // Use sBgItemsIdMap as all the items are already loaded. assertWorkspaceLoaded(); - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { ArrayList<ItemInfo> items = screenItems.get(info.screenId); if (items == null) { @@ -496,7 +458,7 @@ public class LauncherModel extends BroadcastReceiver // can not use sBgWorkspaceScreens because loadWorkspace() may not have been // called. ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context); - synchronized(sBgLock) { + synchronized(sBgDataModel) { for (ItemInfo item : workspaceApps) { if (item instanceof ShortcutInfo) { // Short-circuit this logic if the icon exists somewhere on the workspace @@ -578,7 +540,7 @@ public class LauncherModel extends BroadcastReceiver 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 +581,7 @@ public class LauncherModel extends BroadcastReceiver final long itemId = item.id; Runnable r = new Runnable() { public void run() { - synchronized (sBgLock) { + synchronized (sBgDataModel) { checkItemInfoLocked(itemId, item, stackTrace); } } @@ -675,13 +637,13 @@ public class LauncherModel extends BroadcastReceiver 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 +655,7 @@ public class LauncherModel extends BroadcastReceiver // 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 +664,15 @@ public class LauncherModel extends BroadcastReceiver 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); } } } @@ -856,8 +818,8 @@ public class LauncherModel extends BroadcastReceiver intentWithoutPkg = intent.toUri(0); } - synchronized (sBgLock) { - for (ItemInfo item : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo item : sBgDataModel.itemsIdMap) { if (item instanceof ShortcutInfo) { ShortcutInfo info = (ShortcutInfo) item; Intent targetIntent = info.promisedIntent == null @@ -909,76 +871,35 @@ public class LauncherModel extends BroadcastReceiver public void run() { cr.insert(LauncherSettings.Favorites.CONTENT_URI, values); - // 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) { + static void deleteItemsFromDatabase(Context context, final Iterable<? extends ItemInfo> items) { final ContentResolver cr = context.getContentResolver(); Runnable r = new Runnable() { public void run() { @@ -986,36 +907,7 @@ public class LauncherModel extends BroadcastReceiver 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,39 +915,6 @@ public class LauncherModel extends BroadcastReceiver } /** - * 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. */ @@ -1094,9 +953,9 @@ public class LauncherModel extends BroadcastReceiver throw new RuntimeException(ex); } - synchronized (sBgLock) { - sBgWorkspaceScreens.clear(); - sBgWorkspaceScreens.addAll(screensCopy); + synchronized (sBgDataModel) { + sBgDataModel.workspaceScreens.clear(); + sBgDataModel.workspaceScreens.addAll(screensCopy); } } }; @@ -1111,22 +970,13 @@ public class LauncherModel extends BroadcastReceiver 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); @@ -1153,9 +1003,12 @@ public class LauncherModel extends BroadcastReceiver @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)); + enqueueItemUpdatedTask(new PackageUpdatedTask(op, packages, user)); } @Override @@ -1642,18 +1495,6 @@ public class LauncherModel extends BroadcastReceiver } } - /** 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"); @@ -1666,6 +1507,7 @@ public class LauncherModel extends BroadcastReceiver final boolean isSafeMode = manager.isSafeMode(); final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); final boolean isSdCardReady = Utilities.isBootCompleted(); + final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>(); LauncherAppState app = LauncherAppState.getInstance(); InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); @@ -1696,11 +1538,12 @@ public class LauncherModel extends BroadcastReceiver 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<>(); @@ -1904,12 +1747,7 @@ public class LauncherModel extends BroadcastReceiver // 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 +1818,6 @@ public class LauncherModel extends BroadcastReceiver info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; } - incrementPinnedShortcutCount(key, false /* shouldPin */); } else { // item type == ITEM_TYPE_SHORTCUT info = getShortcutInfo(c, cursorIconInfo); @@ -2022,7 +1859,7 @@ public class LauncherModel extends BroadcastReceiver } // check & update map of what's occupied - if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) { + if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) { itemsToRemove.add(id); break; } @@ -2039,19 +1876,7 @@ public class LauncherModel extends BroadcastReceiver } } - 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 +1884,7 @@ public class LauncherModel extends BroadcastReceiver 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 +1898,16 @@ public class LauncherModel extends BroadcastReceiver 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 +2024,7 @@ public class LauncherModel extends BroadcastReceiver 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 +2043,7 @@ public class LauncherModel extends BroadcastReceiver updateItem(id, values); } } - sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); - sBgAppWidgets.add(appWidgetInfo); + sBgDataModel.addItem(appWidgetInfo, false); } break; } @@ -2242,7 +2057,7 @@ public class LauncherModel extends BroadcastReceiver // Break early if we've stopped loading if (mStopped) { - clearSBgDataStructures(); + sBgDataModel.clear(); return; } @@ -2262,15 +2077,15 @@ public class LauncherModel extends BroadcastReceiver 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); @@ -2278,7 +2093,7 @@ public class LauncherModel extends BroadcastReceiver } // 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 +2116,18 @@ public class LauncherModel extends BroadcastReceiver 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 +2137,8 @@ public class LauncherModel extends BroadcastReceiver // 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 +2351,10 @@ public class LauncherModel extends BroadcastReceiver 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 +2498,8 @@ public class LauncherModel extends BroadcastReceiver 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 +2640,14 @@ public class LauncherModel extends BroadcastReceiver Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded); } if (!mDeepShortcutsLoaded) { - mBgDeepShortcutMap.clear(); + sBgDataModel.deepShortcutMap.clear(); mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission(); if (mHasShortcutHostPermission) { for (UserHandleCompat user : mUserManager.getUserProfiles()) { if (mUserManager.isUserUnlocked(user)) { List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager .queryForAllShortcuts(user); - updateDeepShortcutMap(null, user, shortcuts); + sBgDataModel.updateDeepShortcutMap(null, user, shortcuts); } } } @@ -2844,45 +2662,18 @@ public class LauncherModel extends BroadcastReceiver } 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() { @@ -2917,8 +2708,8 @@ public class LauncherModel extends BroadcastReceiver // If any package icon has changed (app was updated while launcher was dead), // update the corresponding shortcuts. - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo && user.equals(info.user) && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { ShortcutInfo si = (ShortcutInfo) info; @@ -2974,47 +2765,10 @@ public class LauncherModel extends BroadcastReceiver 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; + final int mOp; + final String[] mPackages; + final UserHandleCompat mUser; public static final int OP_NONE = 0; public static final int OP_ADD = 1; @@ -3041,7 +2795,7 @@ public class LauncherModel extends BroadcastReceiver final String[] packages = mPackages; final int N = packages.length; FlagOp flagOp = FlagOp.NO_OP; - StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages))); + final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages)); switch (mOp) { case OP_ADD: { for (int i=0; i<N; i++) { @@ -3091,15 +2845,15 @@ public class LauncherModel extends BroadcastReceiver 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); + mBgAllAppsList.updateDisabledFlags( + ItemInfoMatcher.ofPackages(packageSet, 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); + mBgAllAppsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp); break; } @@ -3148,12 +2902,12 @@ public class LauncherModel extends BroadcastReceiver // 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>(); + final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); + final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>(); + final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>(); - synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap) { + synchronized (sBgDataModel) { + for (ItemInfo info : sBgDataModel.itemsIdMap) { if (info instanceof ShortcutInfo && mUser.equals(info.user)) { ShortcutInfo si = (ShortcutInfo) info; boolean infoUpdated = false; @@ -3161,8 +2915,8 @@ public class LauncherModel extends BroadcastReceiver // Update shortcuts which use iconResource. if ((si.iconResource != null) - && pkgFilter.matches(si.iconResource.packageName)) { - Bitmap icon = Utilities.createIconBitmap( + && packageSet.contains(si.iconResource.packageName)) { + Bitmap icon = LauncherIcons.createIconBitmap( si.iconResource.packageName, si.iconResource.resourceName, context); if (icon != null) { @@ -3173,7 +2927,7 @@ public class LauncherModel extends BroadcastReceiver } ComponentName cn = si.getTargetComponent(); - if (cn != null && pkgFilter.matches(cn.getPackageName())) { + if (cn != null && packageSet.contains(cn.getPackageName())) { AppInfo appInfo = addedOrUpdatedApps.get(cn); if (si.isPromise()) { @@ -3201,11 +2955,6 @@ public class LauncherModel extends BroadcastReceiver } } - // Restore the shortcut. - if (appInfo != null) { - si.flags = appInfo.flags; - } - si.intent = si.promisedIntent; si.promisedIntent = null; si.status = ShortcutInfo.DEFAULT; @@ -3238,7 +2987,7 @@ public class LauncherModel extends BroadcastReceiver LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; if (mUser.equals(widgetInfo.user) && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) - && pkgFilter.matches(widgetInfo.providerName.getPackageName())) { + && packageSet.contains(widgetInfo.providerName.getPackageName())) { widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; @@ -3296,12 +3045,10 @@ public class LauncherModel extends BroadcastReceiver } if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { - for (String pn : removedPackages) { - deletePackageFromDatabase(context, pn, mUser); - } - for (ComponentName cn : removedComponents) { - deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser)); - } + deleteItemsFromDatabase( + context, ItemInfoMatcher.ofPackages(removedPackages, mUser)); + deleteItemsFromDatabase( + context, ItemInfoMatcher.ofComponents(removedComponents, mUser)); // Remove any queued items from the install queue InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); @@ -3386,7 +3133,7 @@ public class LauncherModel extends BroadcastReceiver // 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) { + for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { ShortcutInfo si = (ShortcutInfo) itemInfo; if (si.getPromisedIntent().getPackage().equals(mPackageName) @@ -3435,7 +3182,7 @@ public class LauncherModel extends BroadcastReceiver if (mUpdateIdMap) { // Update the deep shortcut map if the list of ids has changed for an activity. - updateDeepShortcutMap(mPackageName, mUser, mShortcuts); + sBgDataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts); bindDeepShortcuts(); } } @@ -3476,7 +3223,7 @@ public class LauncherModel extends BroadcastReceiver // 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) { + for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT && mUser.equals(itemInfo.user)) { ShortcutInfo si = (ShortcutInfo) itemInfo; @@ -3503,7 +3250,7 @@ public class LauncherModel extends BroadcastReceiver } // Remove shortcut id map for that user - Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator(); + Iterator<ComponentKey> keysIter = sBgDataModel.deepShortcutMap.keySet().iterator(); while (keysIter.hasNext()) { if (keysIter.next().user.equals(mUser)) { keysIter.remove(); @@ -3511,19 +3258,22 @@ public class LauncherModel extends BroadcastReceiver } if (isUserUnlocked) { - updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser)); + sBgDataModel.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,13 +3285,13 @@ public class LauncherModel extends BroadcastReceiver @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); } }); } @@ -3552,27 +3302,6 @@ public class LauncherModel extends BroadcastReceiver 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 +3409,9 @@ public class LauncherModel extends BroadcastReceiver 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. */ @@ -3771,17 +3453,15 @@ public class LauncherModel extends BroadcastReceiver } 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); } } @@ -3805,22 +3485,6 @@ public class LauncherModel extends BroadcastReceiver 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 +3511,8 @@ public class LauncherModel extends BroadcastReceiver * @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 f3d949326..349f09405 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -649,11 +649,8 @@ public class LauncherProvider extends ContentProvider { // 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 @@ public class LauncherProvider extends ContentProvider { } } 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 class LauncherProvider extends ContentProvider { 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 @@ public class LauncherProvider extends ContentProvider { 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 5998dadcd..704481232 100644 --- a/src/com/android/launcher3/LauncherProviderChangeListener.java +++ b/src/com/android/launcher3/LauncherProviderChangeListener.java @@ -7,7 +7,9 @@ package com.android.launcher3; */ 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 eb70650b2..6d5f95159 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -607,6 +607,8 @@ public class LauncherStateTransitionAnimation { playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null, animated, animated, animation, layerViews); + mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); + if (animated) { dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); diff --git a/src/com/android/launcher3/OverviewButtonClickListener.java b/src/com/android/launcher3/OverviewButtonClickListener.java new file mode 100644 index 000000000..c98f1d7bc --- /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 e380e265a..ce6ce6802 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -98,7 +98,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @ViewDebug.ExportedProperty(category = "launcher") protected int mCurrentPage; - protected int mRestorePage = INVALID_RESTORE_PAGE; private int mChildCountOnLastLayout; @ViewDebug.ExportedProperty(category = "launcher") @@ -418,17 +417,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } /** - * 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. */ @@ -879,12 +867,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) { - if (mRestorePage != INVALID_RESTORE_PAGE) { - setCurrentPage(mRestorePage); - mRestorePage = INVALID_RESTORE_PAGE; - } else { - setCurrentPage(getNextPage()); - } + setCurrentPage(getNextPage()); } mChildCountOnLastLayout = childCount; @@ -1099,7 +1082,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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 +1096,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc 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 +1111,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc /** * 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) { diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java index 31820d742..76de3e764 100644 --- a/src/com/android/launcher3/PendingAddItemInfo.java +++ b/src/com/android/launcher3/PendingAddItemInfo.java @@ -20,7 +20,7 @@ import android.content.ComponentName; /** * 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/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java index baeb77c82..84ef12e16 100644 --- a/src/com/android/launcher3/PinchAnimationManager.java +++ b/src/com/android/launcher3/PinchAnimationManager.java @@ -24,6 +24,8 @@ import android.util.Log; 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; @@ -162,9 +164,15 @@ public class PinchAnimationManager { } 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 { diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java index 48a75d111..66209bfa1 100644 --- a/src/com/android/launcher3/PinchToOverviewListener.java +++ b/src/com/android/launcher3/PinchToOverviewListener.java @@ -61,12 +61,12 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG 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 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG // 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; } diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java index b064c47fc..8295b45e8 100644 --- a/src/com/android/launcher3/PreloadIconDrawable.java +++ b/src/com/android/launcher3/PreloadIconDrawable.java @@ -18,7 +18,7 @@ public class PreloadIconDrawable extends Drawable { 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; @@ -242,7 +242,7 @@ public class PreloadIconDrawable extends Drawable { // 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/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index fb9374314..9a9287234 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -31,6 +31,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; 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.ShortcutInfoCompat; /** @@ -147,11 +148,6 @@ public class ShortcutInfo extends ItemInfo { 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} @@ -188,7 +184,6 @@ public class ShortcutInfo extends ItemInfo { 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; @@ -200,7 +195,6 @@ public class ShortcutInfo extends ItemInfo { super(info); title = Utilities.trim(info.title); intent = new Intent(info.intent); - flags = info.flags; isDisabled = info.isDisabled; } @@ -211,7 +205,6 @@ public class ShortcutInfo extends ItemInfo { .getBadgedLabelForUser(info.getLabel(), info.getUser()); intent = AppInfo.makeLaunchIntent(context, info, info.getUser()); itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - flags = AppInfo.initFlags(info); } /** @@ -221,7 +214,6 @@ public class ShortcutInfo extends ItemInfo { public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) { user = shortcutInfo.getUserHandle(); itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; - flags = 0; updateFromDeepShortcutInfo(shortcutInfo, context); } @@ -323,13 +315,13 @@ public class ShortcutInfo extends ItemInfo { 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 +330,9 @@ public class ShortcutInfo extends ItemInfo { 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 91539439c..7ea9aca7c 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -4,6 +4,7 @@ import android.annotation.TargetApi; 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.AttributeSet; 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 @@ public class UninstallDropTarget extends ButtonDropTarget { } } - 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 class UninstallDropTarget extends ButtonDropTarget { 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 @@ public class UninstallDropTarget extends ButtonDropTarget { 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 b0e096a2e..95e3d8269 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -31,19 +31,11 @@ import android.content.pm.PackageManager.NameNotFoundException; 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.PowerManager; @@ -62,11 +54,7 @@ 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; @@ -90,19 +78,9 @@ public final class Utilities { 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]; @@ -170,198 +148,6 @@ public final class Utilities { 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. @@ -832,7 +618,7 @@ public final class Utilities { return ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode(); } - public static boolean isWallapaperAllowed(Context context) { + public static boolean isWallpaperAllowed(Context context) { if (isNycOrAbove()) { try { WallpaperManager wm = context.getSystemService(WallpaperManager.class); @@ -880,28 +666,6 @@ public final class Utilities { 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); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 340177d38..ae3463820 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -28,7 +28,6 @@ import android.annotation.SuppressLint; 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; @@ -53,11 +52,12 @@ import android.view.accessibility.AccessibilityManager; 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; @@ -74,6 +74,7 @@ import com.android.launcher3.dragndrop.SpringLoadedDragController; 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; @@ -303,7 +304,7 @@ public class Workspace extends PagedView 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 +408,12 @@ public class Workspace extends PagedView @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 +470,7 @@ public class Workspace extends PagedView @Override public void onDragEnd() { if (ENFORCE_DRAG_EVENT_ORDER) { - enfoceDragParity("onDragEnd", 0, 0); + enforceDragParity("onDragEnd", 0, 0); } if (!mDeferRemoveExtraEmptyScreen) { @@ -477,6 +483,8 @@ public class Workspace extends PagedView // Re-enable any Un/InstallShortcutReceiver and now process any queued items InstallShortcutReceiver.disableAndFlushInstallQueue(getContext()); + mOutlineProvider = null; + mDragInfo = null; mDragSourceInternal = null; mLauncher.onInteractionEnd(); } @@ -553,24 +561,6 @@ public class Workspace extends PagedView 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 +572,7 @@ public class Workspace extends PagedView /** * 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 +727,7 @@ public class Workspace extends PagedView 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 +748,7 @@ public class Workspace extends PagedView 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, @@ -1470,7 +1452,7 @@ public class Workspace extends PagedView 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 +1461,8 @@ public class Workspace extends PagedView 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); } @@ -1624,7 +1606,7 @@ public class Workspace extends PagedView } @Override - protected void getEdgeVerticalPostion(int[] pos) { + protected void getEdgeVerticalPosition(int[] pos) { View child = getChildAt(getPageCount() - 1); pos[0] = child.getTop(); pos[1] = child.getBottom(); @@ -1756,7 +1738,7 @@ public class Workspace extends PagedView } public boolean isOnOrMovingToCustomContent() { - return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE; + return hasCustomContent() && getNextPage() == 0; } private void updateStateForCustomContent(int screenCenter) { @@ -2009,7 +1991,7 @@ public class Workspace extends PagedView public void exitWidgetResizeMode() { DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.clearAllResizeFrames(); + dragLayer.clearResizeFrame(); } @Override @@ -2270,11 +2252,9 @@ public class Workspace extends PagedView 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 +2328,15 @@ public class Workspace extends PagedView 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 +2412,7 @@ public class Workspace extends PagedView // 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; } } @@ -2710,7 +2688,7 @@ public class Workspace extends PagedView public void run() { if (!isPageMoving() && !mIsSwitchingState) { DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.addResizeFrame(info, hostView, cellLayout); + dragLayer.addResizeFrame(hostView, cellLayout); } } }; @@ -2720,6 +2698,8 @@ public class Workspace extends PagedView 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; @@ -2765,6 +2745,26 @@ public class Workspace extends PagedView } } + 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 +2790,7 @@ public class Workspace extends PagedView @Override public void onDragEnter(DragObject d) { if (ENFORCE_DRAG_EVENT_ORDER) { - enfoceDragParity("onDragEnter", 1, 1); + enforceDragParity("onDragEnter", 1, 1); } mCreateUserFolderOnDrop = false; @@ -2807,7 +2807,7 @@ public class Workspace extends PagedView @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 @@ -2840,14 +2840,14 @@ public class Workspace extends PagedView 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 +2968,7 @@ public class Workspace extends PagedView 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]; @@ -3685,8 +3685,6 @@ public class Workspace extends PagedView && mDragInfo.cell != null) { mDragInfo.cell.setVisibility(VISIBLE); } - mOutlineProvider = null; - mDragInfo = null; if (!isFlingToDelete) { // Fling to delete already exits spring loaded mode after the animation finishes. @@ -3813,7 +3811,7 @@ public class Workspace extends PagedView if (!workspaceInModalState() && !mIsSwitchingState) { super.scrollLeft(); } - Folder openFolder = getOpenFolder(); + Folder openFolder = Folder.getOpen(mLauncher); if (openFolder != null) { openFolder.completeDragExit(); } @@ -3824,7 +3822,7 @@ public class Workspace extends PagedView if (!workspaceInModalState() && !mIsSwitchingState) { super.scrollRight(); } - Folder openFolder = getOpenFolder(); + Folder openFolder = Folder.getOpen(mLauncher); if (openFolder != null) { openFolder.completeDragExit(); } @@ -3843,7 +3841,7 @@ public class Workspace extends PagedView } boolean result = false; - if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) { + if (!workspaceInModalState() && !mIsSwitchingState && Folder.getOpen(mLauncher) == null) { mInScrollArea = true; final int page = getNextPage() + @@ -4003,63 +4001,34 @@ public class Workspace extends PagedView 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); - } - - 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; - } - } - return 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); + if (view.getTag() instanceof ItemInfo) { + ItemInfo item = (ItemInfo) view.getTag(); + items.add(item); + idToViewMap.put(item.id, view); } } - // 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); - } - } + for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) { + View child = idToViewMap.get(itemToRemove.id); - if (childrenToRemove.size() > 0) { - layout.requestLayout(); - layout.invalidate(); + 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); + } + } 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); + } + } } } @@ -4160,8 +4129,9 @@ public class Workspace extends PagedView 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 +4256,7 @@ public class Workspace extends PagedView } @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 +4308,6 @@ public class Workspace extends PagedView @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); } diff --git a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java index 62a9a6d19..f8df5d7be 100644 --- a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java +++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java @@ -28,7 +28,7 @@ import com.android.launcher3.dragndrop.DragOptions; * 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 @@ public class AccessibileDragListenerAdapter implements DragListener { * @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 173aad044..83391f3ec 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -57,7 +57,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme @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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme }); 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme 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 385a766a3..29dd95c1a 100644 --- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java @@ -44,7 +44,7 @@ public class OverviewAccessibilityDelegate extends AccessibilityDelegate { 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 0baa8f3db..f7ca7034d 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -19,6 +19,7 @@ package com.android.launcher3.accessibility; 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 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele } @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 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele 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 77ef64233..768d17bec 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -15,10 +15,7 @@ */ 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.Rect; import android.support.v7.widget.RecyclerView; import android.text.Selection; @@ -31,25 +28,23 @@ import android.util.AttributeSet; 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,95 +53,20 @@ import com.android.launcher3.shortcuts.DeepShortcutsContainer; 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; + LauncherTransitionable, 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; @@ -154,16 +74,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private View mSearchContainer; 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,23 +90,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc 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(); Selection.setSelection(mSearchQueryBuilder, 0); } @@ -282,13 +186,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } // 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 +244,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc 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 +257,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc 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 +270,15 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @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 +289,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc 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 = grid.hotseatBarHeightPx; + mSearchContainer.setLayoutParams(searchContainerLp); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; @@ -412,98 +304,21 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // --- 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 +341,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @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 +353,20 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc 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,24 +417,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } 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; } } @@ -660,55 +445,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } - /** - * 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) { @@ -732,11 +468,29 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @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 76934af7d..28b7685ed 100644 --- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java +++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java @@ -29,12 +29,11 @@ public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb 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 class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallb 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 7b6aef16d..bd877f248 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -18,10 +18,7 @@ package com.android.launcher3.allapps; 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.BubbleTextView; 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 @@ import java.util.List; 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 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // 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 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } } - /** - * 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 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. 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 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } /** - * 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 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. 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 0173847e0..ab34287eb 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -101,7 +101,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView { 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { // 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { // 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { } } - /** - * 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { // 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { int availableScrollBarHeight = getAvailableScrollBarHeight(); int availableScrollHeight = getAvailableScrollHeight(); if (availableScrollHeight <= 0) { - mScrollbar.setThumbOffset(-1, -1); + mScrollbar.setThumbOffsetY(-1); return; } @@ -323,11 +302,10 @@ public class AllAppsRecyclerView extends BaseRecyclerView { // 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { // 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView { } @Override - protected int getVisibleHeight() { - return super.getVisibleHeight() + protected int getScrollbarTrackHeight() { + return super.getScrollbarTrackHeight() - Launcher.getLauncher(getContext()).getDragLayer().getInsets().bottom; } @@ -429,7 +407,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView { 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 b5afb2bd8..6587ad78c 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; 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 @@ import com.android.launcher3.R; * 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 b129bb09d..adfad0813 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -108,7 +108,7 @@ public class AllAppsTransitionController implements TouchController, VerticalPul } @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 @@ public class AllAppsTransitionController implements TouchController, VerticalPul } @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 173065be2..8b7a6ba4d 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.Launcher; 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 @@ public class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { // 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 @@ public class AlphabeticalAppsList { // 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 @@ public class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { } } - /** - * 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 @@ public class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { 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 class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { } /** - * 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 @@ public class AlphabeticalAppsList { // 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 @@ public class AlphabeticalAppsList { } // Add each of the section apps to the list in order - List<AppInfo> allApps = new ArrayList<>(mApps.size()); + mApps.clear(); for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { - allApps.addAll(entry.getValue()); + mApps.addAll(entry.getValue()); } - - mApps.clear(); - mApps.addAll(allApps); } else { // Just compute the section headers for use below for (AppInfo info : mApps) { @@ -403,7 +350,6 @@ public class AlphabeticalAppsList { * 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 @@ public class AlphabeticalAppsList { mFilteredApps.clear(); mFastScrollerSections.clear(); mAdapterItems.clear(); - mSections.clear(); if (DEBUG_PREDICTIONS) { if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) { @@ -429,7 +374,7 @@ public class AlphabeticalAppsList { } // 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 @@ public class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { 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 @@ public class AlphabeticalAppsList { } /** - * 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 index bd28560f3..1f5fece5f 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/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java index ab2b6edd5..96e1299cf 100644 --- a/src/com/android/launcher3/allapps/VerticalPullDetector.java +++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java @@ -230,7 +230,7 @@ public class VerticalPullDetector { 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/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index 4aa667ed0..948471c5e 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -54,7 +54,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { 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 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { 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 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { 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/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java index 156941a29..e968f36a7 100644 --- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java +++ b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java @@ -70,7 +70,7 @@ public class AnotherWindowDragSource implements DragSource { } @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 6eb7dcc20..e11bfc682 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -131,7 +131,7 @@ public class DragController implements DragDriver.EventListener, TouchController 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 @@ public class DragController implements DragDriver.EventListener, TouchController 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 @@ public class DragController implements DragDriver.EventListener, TouchController 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,18 @@ public class DragController implements DragDriver.EventListener, TouchController 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; + } + + public boolean isInPreDrag() { + return mIsInPreDrag; } /** @@ -329,7 +332,9 @@ public class DragController implements DragDriver.EventListener, TouchController 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,7 +355,6 @@ public class DragController implements DragDriver.EventListener, TouchController private void endDrag() { if (isDragging()) { mDragDriver = null; - mOptions = null; clearScrollRunnable(); boolean isDeferred = false; if (mDragObject.dragView != null) { @@ -363,15 +367,24 @@ public class DragController implements DragDriver.EventListener, TouchController // Only end the drag if we are not deferred if (!isDeferred) { - for (DragListener listener : new ArrayList<>(mListeners)) { - listener.onDragEnd(); - } + callOnDragEnd(); } } releaseVelocityTracker(); } + 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 +393,7 @@ public class DragController implements DragDriver.EventListener, TouchController if (mDragObject.deferDragViewCleanupPostAnimation) { // If we skipped calling onDragEnd() before, do it now - for (DragListener listener : new ArrayList<>(mListeners)) { - listener.onDragEnd(); - } + callOnDragEnd(); } } @@ -456,7 +467,7 @@ public class DragController implements DragDriver.EventListener, TouchController /** * 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 +547,9 @@ public class DragController implements DragDriver.EventListener, TouchController 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 +615,7 @@ public class DragController implements DragDriver.EventListener, TouchController /** * 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 +712,7 @@ public class DragController implements DragDriver.EventListener, TouchController (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]; @@ -734,11 +745,15 @@ public class DragController implements DragDriver.EventListener, TouchController } } 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); + } else { + // Only defer the drag view cleanup if the drag source handles the drop. + if (!(mDragObject.dragSource instanceof DropTarget)) { + mDragObject.deferDragViewCleanupPostAnimation = false; + } } } @@ -760,7 +775,7 @@ public class DragController implements DragDriver.EventListener, TouchController dropCoordinates[0] = x; dropCoordinates[1] = y; - mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates); + mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates); return target; } @@ -768,7 +783,7 @@ public class DragController implements DragDriver.EventListener, TouchController return null; } - public void setDragScoller(DragScroller scroller) { + public void setDragScroller(DragScroller scroller) { mDragScroller = scroller; } @@ -777,7 +792,7 @@ public class DragController implements DragDriver.EventListener, TouchController } /** - * 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/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 016347b17..59d18c23a 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -36,9 +36,9 @@ import android.graphics.Region; 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.view.animation.Interpolator; 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.config.FeatureFlags; 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 @@ public class DragLayer extends InsettableFrameLayout { @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 @@ public class DragLayer extends InsettableFrameLayout { 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 class DragLayer extends InsettableFrameLayout { } 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 @@ public class DragLayer extends InsettableFrameLayout { } 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 @@ public class DragLayer extends InsettableFrameLayout { } 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 @@ public class DragLayer extends InsettableFrameLayout { 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 @@ public class DragLayer extends InsettableFrameLayout { @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 @@ public class DragLayer extends InsettableFrameLayout { @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 @@ public class DragLayer extends InsettableFrameLayout { @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,22 +382,8 @@ public class DragLayer extends InsettableFrameLayout { 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; } @@ -534,7 +480,7 @@ public class DragLayer extends InsettableFrameLayout { /** * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. */ - public float mapCoordInSelfToDescendent(View descendant, int[] coord) { + public float mapCoordInSelfToDescendant(View descendant, int[] coord) { return Utilities.mapCoordInSelfToDescendent(descendant, this, coord); } @@ -556,7 +502,7 @@ public class DragLayer extends InsettableFrameLayout { @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 class DragLayer extends InsettableFrameLayout { } } - 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 boolean isWidgetBeingResized() { - return mCurrentResizeFrame != null; - } - - public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, - CellLayout cellLayout) { - AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), - widget, cellLayout, this); - - LayoutParams lp = new LayoutParams(-1, -1); - lp.customPosition = true; + public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) { + clearResizeFrame(); - addView(resizeFrame, lp); - mResizeFrames.add(resizeFrame); + mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher) + .inflate(R.layout.app_widget_resize_frame, this, false); + mCurrentResizeFrame.setupForWidget(widget, cellLayout, this); + ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true; - resizeFrame.snapToWidget(false); + addView(mCurrentResizeFrame); + mCurrentResizeFrame.snapToWidget(false); } public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, @@ -1077,7 +1011,7 @@ public class DragLayer extends InsettableFrameLayout { @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 @@ public class DragLayer extends InsettableFrameLayout { @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 dbf46f338..906855a7c 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 @@ public class DragOptions { /** 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 @@ public class DragOptions { * 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/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java index 6b14be714..ac50332bf 100644 --- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java +++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java @@ -23,10 +23,10 @@ import android.graphics.Paint; 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 @@ public class ExternalDragPreviewProvider extends DragPreviewProvider { 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/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java index 6dc0035ee..1e663a9ae 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 class ExtractionUtils { 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; /** @@ -73,14 +72,10 @@ public class ExtractionUtils { 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.isNycOrAbove() ? + 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 698e5aa04..2952196fa 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.AccelerateInterpolator; 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.ShortcutInfo; 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 @@ import java.util.Comparator; /** * Represents a set of icons chosen by the user or generated by the system. */ -public class Folder extends LinearLayout implements DragSource, View.OnClickListener, +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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 class Folder extends LinearLayout implements DragSource, View.OnClickList 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 class Folder extends LinearLayout implements DragSource, View.OnClickList }); } - 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // 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(); - - AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getFolderHeight(); - - 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); - - Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty); - drift.setDuration(mMaterialExpandDuration); - drift.setStartDelay(mMaterialExpandStagger); - drift.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); - - Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(), - (int) getPivotY(), 0, radius).createRevealAnimator(this); - reveal.setDuration(mMaterialExpandDuration); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - mContent.setAlpha(0f); - Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f); - iconsAlpha.setDuration(mMaterialExpandDuration); - iconsAlpha.setStartDelay(mMaterialExpandStagger); - iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - - 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() { + 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); + + Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty); + drift.setDuration(mMaterialExpandDuration); + drift.setStartDelay(mMaterialExpandStagger); + drift.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); + + Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(), + (int) getPivotY(), 0, radius).createRevealAnimator(this); + reveal.setDuration(mMaterialExpandDuration); + reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); + + mContent.setAlpha(0f); + Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f); + iconsAlpha.setDuration(mMaterialExpandDuration); + iconsAlpha.setStartDelay(mMaterialExpandStagger); + iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); + + 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); + + 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 class Folder extends LinearLayout implements DragSource, View.OnClickList }; 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 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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,7 +1331,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mIsExternalDrag = false; } else { currentDragView = mCurrentDragView; - if (!mDragController.isDeferringDrag()) { + // The view was never removed from this folder if we are still in the pre-drag. + if (!mDragController.isInPreDrag()) { mContent.addViewForRank(currentDragView, si, mEmptyCellRank); } } @@ -1332,7 +1355,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mItemsInvalidated = true; rearrangeChildren(); - if (!mDragController.isDeferringDrag()) { + // The ShortcutInfo was never removed if we are still in the pre-drag. + if (!mDragController.isInPreDrag()) { // Temporarily suppress the listener, as we did all the work already here. try (SuppressInfoChanges s = new SuppressInfoChanges()) { mInfo.add(si, false); @@ -1382,8 +1406,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList rearrangeChildren(); } if (getItemCount() <= 1) { - if (mInfo.opened) { - mLauncher.closeFolder(this, true); + if (mIsOpen) { + close(true); } else { replaceFolderWithFinalItem(); } @@ -1429,7 +1453,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (hasFocus) { startEditingFolderName(); } else { - dismissEditingName(); + mFolderName.dispatchBackKey(); } } } @@ -1442,7 +1466,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } @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 +1552,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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 69c2b0fa3..a29a94659 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -18,6 +18,7 @@ package com.android.launcher3.folder; 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.IconCache; 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 @@ public class FolderIcon extends FrameLayout implements FolderListener { 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 @@ public class FolderIcon extends FrameLayout implements FolderListener { 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 @@ public class FolderIcon extends FrameLayout implements FolderListener { OnAlarmListener mOnOpenListener = new OnAlarmListener() { public void onAlarm(Alarm alarm) { mFolder.beginExternalDrag(); - mLauncher.openFolder(FolderIcon.this); + mFolder.animateOpen(); } }; @@ -974,12 +971,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - } - - @Override public void cancelLongPress() { super.cancelLongPress(); mLongPressHelper.cancelLongPress(); @@ -990,13 +981,76 @@ public class FolderIcon extends FrameLayout implements FolderListener { 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 7e7ee3472..dcd3ec42f 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -679,7 +679,7 @@ public class FolderPagedView extends PagedView { } @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 000000000..c4f3ee15c --- /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 bc91c15be..a7d4c63a1 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable; 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 @@ public class DragPreviewProvider { // 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 class DragPreviewProvider { } 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 @@ public class DragPreviewProvider { 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 index 9dec7d9e4..9c397210e 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.Rect; 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 @@ public class HolographicOutlineHelper { 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 @@ public class HolographicOutlineHelper { 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 @@ public class HolographicOutlineHelper { } 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 @@ public class HolographicOutlineHelper { 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 index 040a1b51a..14109172b 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 @@ public class IconNormalizer { } /** - * 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 @@ public class IconNormalizer { 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 @@ public class IconNormalizer { 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 @@ public class IconNormalizer { } // 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 000000000..9f3f3571d --- /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 000000000..6603e93b0 --- /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 7672f5a79..b0d6b2dbf 100644 --- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java +++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java @@ -143,7 +143,7 @@ public abstract class FocusIndicatorHelper implements } 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 56fdce834..441d8e5f5 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -37,7 +37,7 @@ import java.util.Locale; /** * 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 */ @@ -48,13 +48,9 @@ public class UserEventDispatcher { 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. @@ -64,13 +60,13 @@ public class UserEventDispatcher { * @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) { @@ -82,8 +78,8 @@ public class UserEventDispatcher { // 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(); } @@ -123,12 +119,12 @@ public class UserEventDispatcher { // 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(); @@ -166,24 +162,31 @@ public class UserEventDispatcher { } 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) { @@ -198,11 +201,11 @@ public class UserEventDispatcher { 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/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java deleted file mode 100644 index 5f80037dc..000000000 --- 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 000000000..c18eeef3d --- /dev/null +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -0,0 +1,237 @@ +/* + * 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.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) { + LauncherAppState.getInstance() + .getShortcutManager().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) { + LauncherAppState.getInstance().getShortcutManager() + .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/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java index c86ba86fd..00470e1ea 100644 --- a/src/com/android/launcher3/model/PackageItemInfo.java +++ b/src/com/android/launcher3/model/PackageItemInfo.java @@ -40,12 +40,6 @@ public class PackageItemInfo extends ItemInfo { */ 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/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java new file mode 100644 index 000000000..54260c915 --- /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/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index b2a94bb34..3953f3964 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -18,7 +18,9 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.compat.AlphabeticIndexCompat; 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; @@ -37,74 +39,31 @@ public class WidgetsModel { private static final String TAG = "WidgetsModel"; private static final boolean DEBUG = false; - /* List of packages that is tracked by this model. */ - private 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<>(); - } - - @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); + mWidgetsList = new MultiHashMap<>(); } - 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()) { @@ -132,11 +91,10 @@ public class WidgetsModel { 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 +105,9 @@ public class WidgetsModel { // 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 +135,20 @@ public class WidgetsModel { 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 fb9d2f7fe..12a67014d 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -80,7 +80,7 @@ public class PageIndicatorDots extends PageIndicator { @Override public void onAnimationEnd(Animator animation) { mAnimator = null; - animateToPostion(mFinalPosition); + animateToPosition(mFinalPosition); } }; @@ -136,22 +136,25 @@ public class PageIndicatorDots extends PageIndicator { 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 faa5fad12..2f8241907 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -56,7 +56,7 @@ public class LauncherDbUtils { 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 index 6a2bce05d..5379336de 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.graphics.Paint; 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 index 02d8a13ff..c83143be9 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.View; 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 @@ public class QsbContainerView extends FrameLayout { 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 class QsbContainerView extends FrameLayout { 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 = -1; + // widgetId is already bound and its not the correct provider. reset host. + mQsbWidgetHost.deleteHost(); } - widgetId = widgetHost.allocateAppWidgetId(); + widgetId = mQsbWidgetHost.allocateAppWidgetId(); isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts); if (!isWidgetBound) { - widgetHost.deleteAppWidgetId(widgetId); + mQsbWidgetHost.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; } - @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); - } + private void saveWidgetId(int widgetId) { + Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply(); } @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(QSB_WIDGET_ID, sSavedWidgetId); + public void onClick(View view) { + // 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 @@ public class QsbContainerView extends FrameLayout { @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 @@ public class QsbContainerView extends FrameLayout { } 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 000000000..8b6fa1651 --- /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 49d6fa932..9037af4d2 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -42,13 +42,8 @@ import java.util.List; 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 = - FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST; + private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC + | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED; private final LauncherApps mLauncherApps; private boolean mWasLastCallSuccess; @@ -86,7 +81,7 @@ public class DeepShortcutManager { */ 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); } @@ -172,7 +167,7 @@ public class DeepShortcutManager { */ 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) { diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java index 2702d4e8e..b5126e95c 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java @@ -41,6 +41,7 @@ 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; @@ -73,9 +74,9 @@ import java.util.List; * 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,7 +86,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate; private final boolean mIsRtl; - private BubbleTextView mDeferredDragIcon; + private BubbleTextView mOriginalIcon; private final Rect mTempRect = new Rect(); private Point mIconLastTouchPos = new Point(); private boolean mIsLeftAligned; @@ -94,7 +95,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC private Animator mOpenCloseAnimator; private boolean mDeferContainerRemoval; - private boolean mIsOpen; public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); @@ -150,7 +150,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC 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 +376,9 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC 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,30 +387,26 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC * 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); + } } } }; @@ -452,7 +445,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC 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 +500,29 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } 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 +602,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC if (mDeferContainerRemoval) { setVisibility(INVISIBLE); } else { - close(); + closeComplete(); } } }); @@ -609,25 +610,30 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC 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 +642,13 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC */ 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 +677,11 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC 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 2adb82e2d..fc474f527 100644 --- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java +++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java @@ -23,7 +23,7 @@ import android.graphics.Rect; 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 class ShortcutDragPreviewProvider extends DragPreviewProvider { 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/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java index 4fefa986e..6603ee765 100644 --- a/src/com/android/launcher3/util/CursorIconInfo.java +++ b/src/com/android/launcher3/util/CursorIconInfo.java @@ -25,6 +25,7 @@ import android.text.TextUtils; 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 @@ public class CursorIconInfo { 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 @@ public class CursorIconInfo { * 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 163c953bb..afc45fe35 100644 --- a/src/com/android/launcher3/util/FocusLogic.java +++ b/src/com/android/launcher3/util/FocusLogic.java @@ -79,8 +79,7 @@ public class FocusLogic { 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 46e9184b4..8f985c344 100644 --- a/src/com/android/launcher3/util/ItemInfoMatcher.java +++ b/src/com/android/launcher3/util/ItemInfoMatcher.java @@ -18,7 +18,9 @@ package com.android.launcher3.util; 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 class ItemInfoMatcher { 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 000000000..5da9ddfda --- /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/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java index 3b258e4a5..c8a5ffb1a 100644 --- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java +++ b/src/com/android/launcher3/util/NoLocaleSqliteContext.java @@ -11,9 +11,6 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; */ 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 class NoLocaleSqliteContext extends ContextWrapper { 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/StringFilter.java b/src/com/android/launcher3/util/StringFilter.java deleted file mode 100644 index f539ad11e..000000000 --- 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 d1409c8b9..3cca21500 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 eaa0bb3d5..7c06701e8 100644 --- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java +++ b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java @@ -21,7 +21,7 @@ import android.graphics.Canvas; 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 @@ public class PendingItemPreviewProvider extends DragPreviewProvider { // 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 000000000..b5aaeb9fe --- /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 000000000..3e89eeb9b --- /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 89c44c859..2e1294251 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -21,7 +21,6 @@ import android.graphics.Bitmap; 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.view.ViewGroup; 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.PendingAddItemInfo; 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 @@ public class WidgetsContainerView extends BaseContainerView 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 @@ public class WidgetsContainerView extends BaseContainerView } @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 @@ public class WidgetsContainerView extends BaseContainerView 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 @@ public class WidgetsContainerView extends BaseContainerView @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 @@ public class WidgetsContainerView extends BaseContainerView } 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 @@ public class WidgetsContainerView extends BaseContainerView } 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 @@ public class WidgetsContainerView extends BaseContainerView /** * 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 @@ public class WidgetsContainerView extends BaseContainerView } @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 6b8ea496e..a5846ec33 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; 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 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { 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 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { 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 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { } // 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 class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { 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 2560661c1..e0a80c679 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 @@ import com.android.launcher3.model.WidgetsModel; 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 @@ public class WidgetsRecyclerView extends BaseRecyclerView { 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 @@ public class WidgetsRecyclerView extends BaseRecyclerView { // 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 @@ public class WidgetsRecyclerView extends BaseRecyclerView { // 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 @@ public class WidgetsRecyclerView extends BaseRecyclerView { @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/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java index 5c5069fea..c133bf6c8 100644 --- a/tests/src/com/android/launcher3/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/BindWidgetTest.java @@ -232,7 +232,6 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase { 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/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java index e858d17f3..e94fca6ff 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.InvariantDeviceProfile; 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; @@ -189,7 +188,6 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase { 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/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java index eee567fb8..79aed806c 100644 --- a/tests/src/com/android/launcher3/util/FocusLogicTest.java +++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java @@ -49,8 +49,6 @@ public final class FocusLogicTest extends AndroidTestCase { 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() { |