diff --git a/Android.mk b/Android.mk
index 1cf15beeed..d3823c3d5f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -33,7 +33,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) \
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/WallpaperPicker/res \
$(LOCAL_PATH)/res \
- $(LOCAL_PATH)/../../../frameworks/support/v7/recyclerview/res
+ frameworks/support/v7/recyclerview/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
@@ -54,40 +54,20 @@ include $(BUILD_PACKAGE)
#
-# Protocol Buffer Debug Utility in Java
+# Launcher proto buffer jar used for development
#
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under, util) \
- $(call all-proto-files-under, protos)
+LOCAL_SRC_FILES := $(call all-proto-files-under, protos)
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := launcher_protoutil_lib
+LOCAL_MODULE := launcher_proto_lib
LOCAL_IS_HOST_MODULE := true
-LOCAL_JAR_MANIFEST := util/etc/manifest.txt
+LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
include $(BUILD_HOST_JAVA_LIBRARY)
-#
-# Protocol Buffer Debug Utility Wrapper Script
-#
-include $(CLEAR_VARS)
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := launcher_protoutil
-
-include $(BUILD_SYSTEM)/base_rules.mk
-
-$(LOCAL_BUILT_MODULE): | $(HOST_OUT_JAVA_LIBRARIES)/launcher_protoutil_lib.jar
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/util/etc/launcher_protoutil | $(ACP)
- @echo "Copy: $(PRIVATE_MODULE) ($@)"
- $(copy-file-to-new-target)
- $(hide) chmod 755 $@
-
-INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE)
-
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 48dcf4ee7f..918ae52aaa 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -81,6 +81,7 @@
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor"
+ android:configChanges="keyboard|keyboardHidden|navigation"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
@@ -92,39 +93,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/WallpaperPicker/res/anim/fade_out.xml b/WallpaperPicker/res/anim/fade_out.xml
new file mode 100644
index 0000000000..2749d9280b
--- /dev/null
+++ b/WallpaperPicker/res/anim/fade_out.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/WallpaperPicker/res/layout/actionbar_set_wallpaper.xml b/WallpaperPicker/res/layout/actionbar_set_wallpaper.xml
index 8e349b7324..2b39b09527 100644
--- a/WallpaperPicker/res/layout/actionbar_set_wallpaper.xml
+++ b/WallpaperPicker/res/layout/actionbar_set_wallpaper.xml
@@ -28,5 +28,6 @@
android:drawableLeft="@drawable/ic_actionbar_accept"
android:drawablePadding="8dp"
android:gravity="start|center_vertical"
- android:text="@string/wallpaper_instructions">
+ android:text="@string/wallpaper_instructions"
+ android:enabled="false">
diff --git a/WallpaperPicker/res/values-af/strings.xml b/WallpaperPicker/res/values-af/strings.xml
index eb816402a7..bc87fe1213 100644
--- a/WallpaperPicker/res/values-af/strings.xml
+++ b/WallpaperPicker/res/values-af/strings.xml
@@ -22,6 +22,7 @@
"Stel muurpapier""Kon nie prent laai nie""Kon nie prent as muurpapier laai nie"
+ "Kon nie prent as muurpapier stel nie""%1$d gekies""%1$d gekies"
diff --git a/WallpaperPicker/res/values-am/strings.xml b/WallpaperPicker/res/values-am/strings.xml
index 7e793856ec..23bf538e3c 100644
--- a/WallpaperPicker/res/values-am/strings.xml
+++ b/WallpaperPicker/res/values-am/strings.xml
@@ -22,6 +22,7 @@
"ልጣፍ አዘጋጅ""ምስሉን መጫን አልተቻለም""ምስሉን እንደ ግድግዳ ወረቀት መጫን አልተቻለም"
+ "ምስሉን እንደ ግድግዳ ወረቀት ማዘጋጀት አልተቻለም""%1$d ተመርጧል""%1$d ተመርጧል"
diff --git a/WallpaperPicker/res/values-ar/strings.xml b/WallpaperPicker/res/values-ar/strings.xml
index 84fa104db2..db6834c9ad 100644
--- a/WallpaperPicker/res/values-ar/strings.xml
+++ b/WallpaperPicker/res/values-ar/strings.xml
@@ -22,6 +22,7 @@
"تعيين الخلفية""تعذر تحميل الصورة""تعذر تحميل الصورة كخلفية"
+ "تعذر تعيين الصورة كخلفية""تم تحديد %1$d""تم تحديد %1$d"
diff --git a/WallpaperPicker/res/values-bg/strings.xml b/WallpaperPicker/res/values-bg/strings.xml
index 60ae3022f3..bf2a83b1ee 100644
--- a/WallpaperPicker/res/values-bg/strings.xml
+++ b/WallpaperPicker/res/values-bg/strings.xml
@@ -22,6 +22,7 @@
"Задаване на тапета""Изображението не можа да бъде заредено""Изображението не можа да бъде заредено като тапет"
+ "Изображението не можа да бъде зададено като тапет""Избрахте %1$d""Избрахте %1$d"
diff --git a/WallpaperPicker/res/values-bn-rBD/strings.xml b/WallpaperPicker/res/values-bn-rBD/strings.xml
index 74d7e57bda..1c4d3d493c 100644
--- a/WallpaperPicker/res/values-bn-rBD/strings.xml
+++ b/WallpaperPicker/res/values-bn-rBD/strings.xml
@@ -22,6 +22,7 @@
"ওয়ালপেপার সেট করুন""চিত্র লোড করা যায়নি""ওয়ালপেপার হিসাবে চিত্র লোড করা যায়নি"
+ "ওয়ালপেপার হিসাবে চিত্র সেট করা যায়নি""%1$dটি নির্বাচন করা হয়েছে""%1$dটি নির্বাচন করা হয়েছে"
diff --git a/WallpaperPicker/res/values-ca/strings.xml b/WallpaperPicker/res/values-ca/strings.xml
index cbec76250e..9533ae918c 100644
--- a/WallpaperPicker/res/values-ca/strings.xml
+++ b/WallpaperPicker/res/values-ca/strings.xml
@@ -22,6 +22,7 @@
"Estableix el fons de pantalla""No s\'ha pogut carregar la imatge.""No s\'ha pogut carregar la imatge com a fons de pantalla."
+ "No s\'ha pogut definir la imatge com a fons de pantalla""Seleccionats: %1$d""Seleccionats: %1$d"
diff --git a/WallpaperPicker/res/values-cs/strings.xml b/WallpaperPicker/res/values-cs/strings.xml
index da0758e0ab..aab8cc8b34 100644
--- a/WallpaperPicker/res/values-cs/strings.xml
+++ b/WallpaperPicker/res/values-cs/strings.xml
@@ -22,6 +22,7 @@
"Nastavit jako tapetu""Obrázek nelze načíst.""Obrázek nelze načíst jako tapetu."
+ "Obrázek nelze nastavit jako tapetu""Vybráno: %1$d""Vybráno: %1$d"
diff --git a/WallpaperPicker/res/values-da/strings.xml b/WallpaperPicker/res/values-da/strings.xml
index 12c0fe87e6..10ef5b3da0 100644
--- a/WallpaperPicker/res/values-da/strings.xml
+++ b/WallpaperPicker/res/values-da/strings.xml
@@ -22,6 +22,7 @@
"Angiv baggrund""Billedet kunne ikke indlæses""Billedet kunne ikke indlæses som baggrund"
+ "Billedet kunne ikke indlæses som baggrund""%1$d er valgt""%1$d er valgt"
diff --git a/WallpaperPicker/res/values-de/strings.xml b/WallpaperPicker/res/values-de/strings.xml
index 3171b3f3a7..be35b736bd 100644
--- a/WallpaperPicker/res/values-de/strings.xml
+++ b/WallpaperPicker/res/values-de/strings.xml
@@ -22,6 +22,7 @@
"Hintergrund auswählen""Bild konnte nicht geladen werden.""Bild konnte nicht als Hintergrund geladen werden."
+ "Bild konnte nicht als Hintergrund festgelegt werden.""%1$d ausgewählt""%1$d ausgewählt"
diff --git a/WallpaperPicker/res/values-el/strings.xml b/WallpaperPicker/res/values-el/strings.xml
index 2988beced4..1d799a86d5 100644
--- a/WallpaperPicker/res/values-el/strings.xml
+++ b/WallpaperPicker/res/values-el/strings.xml
@@ -22,6 +22,7 @@
"Ορισμός ταπετσαρίας""Δεν ήταν δυνατή η φόρτωση της εικόνας""Δεν ήταν δυνατή η φόρτωση της εικόνας ως ταπετσαρία"
+ "Δεν ήταν δυνατός ο ορισμός της εικόνας ως ταπετσαρία""%1$d επιλεγμένα""%1$d επιλεγμένα"
diff --git a/WallpaperPicker/res/values-en-rAU/strings.xml b/WallpaperPicker/res/values-en-rAU/strings.xml
index 30450ee814..a384ff6083 100644
--- a/WallpaperPicker/res/values-en-rAU/strings.xml
+++ b/WallpaperPicker/res/values-en-rAU/strings.xml
@@ -22,6 +22,7 @@
"Set wallpaper""Couldn\'t load image""Couldn\'t load image as wallpaper"
+ "Couldn\'t set image as wallpaper""%1$d selected""%1$d selected"
diff --git a/WallpaperPicker/res/values-en-rGB/strings.xml b/WallpaperPicker/res/values-en-rGB/strings.xml
index 30450ee814..a384ff6083 100644
--- a/WallpaperPicker/res/values-en-rGB/strings.xml
+++ b/WallpaperPicker/res/values-en-rGB/strings.xml
@@ -22,6 +22,7 @@
"Set wallpaper""Couldn\'t load image""Couldn\'t load image as wallpaper"
+ "Couldn\'t set image as wallpaper""%1$d selected""%1$d selected"
diff --git a/WallpaperPicker/res/values-en-rIN/strings.xml b/WallpaperPicker/res/values-en-rIN/strings.xml
index 30450ee814..a384ff6083 100644
--- a/WallpaperPicker/res/values-en-rIN/strings.xml
+++ b/WallpaperPicker/res/values-en-rIN/strings.xml
@@ -22,6 +22,7 @@
"Set wallpaper""Couldn\'t load image""Couldn\'t load image as wallpaper"
+ "Couldn\'t set image as wallpaper""%1$d selected""%1$d selected"
diff --git a/WallpaperPicker/res/values-es-rUS/strings.xml b/WallpaperPicker/res/values-es-rUS/strings.xml
index c18ecb2334..924ea56155 100644
--- a/WallpaperPicker/res/values-es-rUS/strings.xml
+++ b/WallpaperPicker/res/values-es-rUS/strings.xml
@@ -22,6 +22,7 @@
"Establecer como fondo de pantalla""No se pudo cargar la imagen.""No se pudo cargar la imagen como fondo de pantalla."
+ "No se pudo establecer la imagen como fondo de pantalla""%1$d seleccionado""%1$d seleccionado"
diff --git a/WallpaperPicker/res/values-es/strings.xml b/WallpaperPicker/res/values-es/strings.xml
index b7221af2ae..8ecd3f4c36 100644
--- a/WallpaperPicker/res/values-es/strings.xml
+++ b/WallpaperPicker/res/values-es/strings.xml
@@ -22,6 +22,7 @@
"Establecer fondo""No se ha podido cargar la imagen""No se ha podido cargar la imagen como fondo de pantalla"
+ "No se ha podido establecer la imagen como fondo de pantalla""Seleccionados: %1$d""Seleccionados: %1$d"
diff --git a/WallpaperPicker/res/values-et-rEE/strings.xml b/WallpaperPicker/res/values-et-rEE/strings.xml
index 571a0ffbdd..59ca7704c4 100644
--- a/WallpaperPicker/res/values-et-rEE/strings.xml
+++ b/WallpaperPicker/res/values-et-rEE/strings.xml
@@ -22,6 +22,7 @@
"Määra taustapilt""Kujutist ei õnnestunud laadida""Kujutist ei õnnestunud taustapildina laadida"
+ "Kujutist ei õnnestunud taustapildiks määrata""Valitud on %1$d""Valitud on %1$d"
diff --git a/WallpaperPicker/res/values-eu-rES/strings.xml b/WallpaperPicker/res/values-eu-rES/strings.xml
index 45bf7a1061..fb77b1acd5 100644
--- a/WallpaperPicker/res/values-eu-rES/strings.xml
+++ b/WallpaperPicker/res/values-eu-rES/strings.xml
@@ -22,6 +22,7 @@
"Ezarri horma-papera""Ezin izan da irudia kargatu""Ezin izan da irudia horma-paper gisa kargatu"
+ "Ezin izan da ezarri irudia horma-paper gisa""%1$d hautatuta""%1$d hautatuta"
diff --git a/WallpaperPicker/res/values-fa/strings.xml b/WallpaperPicker/res/values-fa/strings.xml
index 469ec1263c..da4b7a10d4 100644
--- a/WallpaperPicker/res/values-fa/strings.xml
+++ b/WallpaperPicker/res/values-fa/strings.xml
@@ -22,6 +22,7 @@
"تنظیم کاغذدیواری""تصویر بارگیری نشد""تصویر بهعنوان کاغذدیواری بارگیری نشد"
+ "تصویر بهعنوان کاغذدیواری تنظیم نشد""%1$d انتخاب شد""%1$d انتخاب شد"
diff --git a/WallpaperPicker/res/values-fi/strings.xml b/WallpaperPicker/res/values-fi/strings.xml
index c82d3e0edd..3c8f1f538c 100644
--- a/WallpaperPicker/res/values-fi/strings.xml
+++ b/WallpaperPicker/res/values-fi/strings.xml
@@ -22,6 +22,7 @@
"Aseta taustakuva""Kuvan lataus epäonnistui""Kuvaa ei voitu ladata taustakuvaksi"
+ "Kuvan asettaminen taustakuvaksi epäonnistui.""%1$d valittu""%1$d valittu"
diff --git a/WallpaperPicker/res/values-fr-rCA/strings.xml b/WallpaperPicker/res/values-fr-rCA/strings.xml
index 92568966ee..ba1d430002 100644
--- a/WallpaperPicker/res/values-fr-rCA/strings.xml
+++ b/WallpaperPicker/res/values-fr-rCA/strings.xml
@@ -22,6 +22,7 @@
"Définir le fond d\'écran""Impossible de charger l\'image""Impossible de charger l\'image comme fond d\'écran"
+ "Impossible d\'utiliser l\'image comme fond d\'écran""%1$d sélectionné""%1$d sélectionné"
diff --git a/WallpaperPicker/res/values-fr/strings.xml b/WallpaperPicker/res/values-fr/strings.xml
index 479a7e4303..9f3b525311 100644
--- a/WallpaperPicker/res/values-fr/strings.xml
+++ b/WallpaperPicker/res/values-fr/strings.xml
@@ -22,6 +22,7 @@
"Définir comme fond d\'écran""Impossible de charger l\'image.""Impossible de charger l\'image comme fond d\'écran."
+ "Impossible de définir l\'image comme fond d\'écran.""%1$d élément sélectionné""%1$d élément sélectionné"
diff --git a/WallpaperPicker/res/values-gl-rES/strings.xml b/WallpaperPicker/res/values-gl-rES/strings.xml
index 0396b2f628..5805489a7d 100644
--- a/WallpaperPicker/res/values-gl-rES/strings.xml
+++ b/WallpaperPicker/res/values-gl-rES/strings.xml
@@ -22,6 +22,7 @@
"Establecer fondo de pantalla""Non se puido cargar a imaxe""Non se puido cargar a imaxe como fondo de pantalla"
+ "Non se puido definir a imaxe como fondo de pantalla""Seleccionaches %1$d""Seleccionaches %1$d"
diff --git a/WallpaperPicker/res/values-hi/strings.xml b/WallpaperPicker/res/values-hi/strings.xml
index 60834e103d..6f610b5bb5 100644
--- a/WallpaperPicker/res/values-hi/strings.xml
+++ b/WallpaperPicker/res/values-hi/strings.xml
@@ -22,6 +22,7 @@
"वॉलपेपर सेट करें""चित्र लोड नहीं किया जा सका""चित्र को वॉलपेपर के रूप में लोड नहीं किया जा सका"
+ "चित्र को वॉलपेपर के रूप में सेट नहीं किया जा सका""%1$d चयनित""%1$d चयनित"
diff --git a/WallpaperPicker/res/values-hr/strings.xml b/WallpaperPicker/res/values-hr/strings.xml
index 9ed702c5ab..aaf5e1bcd0 100644
--- a/WallpaperPicker/res/values-hr/strings.xml
+++ b/WallpaperPicker/res/values-hr/strings.xml
@@ -22,6 +22,7 @@
"Postavi pozadinu""Nije moguće učitati sliku""Nije moguće učitati sliku kao pozadinu"
+ "Postavljanje slike kao pozadine nije uspjelo""Odabrano je %1$d stavki""Odabrana je %1$d stavka"
diff --git a/WallpaperPicker/res/values-hu/strings.xml b/WallpaperPicker/res/values-hu/strings.xml
index d8b08fdfd3..06c0952a40 100644
--- a/WallpaperPicker/res/values-hu/strings.xml
+++ b/WallpaperPicker/res/values-hu/strings.xml
@@ -22,6 +22,7 @@
"Háttérkép beállítása""A kép betöltése nem sikerült""A kép betöltése háttérképként nem sikerült"
+ "A kép beállítása háttérképként nem sikerült""%1$d kiválasztva""%1$d kiválasztva"
diff --git a/WallpaperPicker/res/values-hy-rAM/strings.xml b/WallpaperPicker/res/values-hy-rAM/strings.xml
index 027b841a73..f68d49f903 100644
--- a/WallpaperPicker/res/values-hy-rAM/strings.xml
+++ b/WallpaperPicker/res/values-hy-rAM/strings.xml
@@ -22,6 +22,7 @@
"Սահմանել պաստառը""Չհաջողվեց բեռնել նկարը""Չհաջողվեց նկարը սահմանել որպես պաստառ"
+ "Չհաջողվեց նկարը դնել որպես պաստառ""%1$d ընտրված""%1$d ընտրված"
diff --git a/WallpaperPicker/res/values-in/strings.xml b/WallpaperPicker/res/values-in/strings.xml
index 91554522f6..634eb1f765 100644
--- a/WallpaperPicker/res/values-in/strings.xml
+++ b/WallpaperPicker/res/values-in/strings.xml
@@ -22,6 +22,7 @@
"Setel wallpaper""Tidak dapat memuat gambar""Tidak dapat memuat gambar sebagai wallpaper"
+ "Tidak dapat menyetel gambar sebagai wallpaper""%1$d dipilih""%1$d dipilih"
diff --git a/WallpaperPicker/res/values-is-rIS/strings.xml b/WallpaperPicker/res/values-is-rIS/strings.xml
index 69e6cd4987..eac44ec2ad 100644
--- a/WallpaperPicker/res/values-is-rIS/strings.xml
+++ b/WallpaperPicker/res/values-is-rIS/strings.xml
@@ -22,6 +22,7 @@
"Velja veggfóður""Ekki var hægt að hlaða mynd""Ekki var hægt að hlaða mynd sem veggfóður"
+ "Ekki var hægt að nota mynd sem veggfóður""%1$d valin""%1$d valið"
diff --git a/WallpaperPicker/res/values-it/strings.xml b/WallpaperPicker/res/values-it/strings.xml
index f4d38d14e0..fdb0ce8de7 100644
--- a/WallpaperPicker/res/values-it/strings.xml
+++ b/WallpaperPicker/res/values-it/strings.xml
@@ -22,6 +22,7 @@
"Imposta sfondo""Impossibile caricare l\'immagine""Impossibile caricare l\'immagine come sfondo"
+ "Impossibile impostare l\'immagine come sfondo""%1$d selezionati""%1$d selezionato"
diff --git a/WallpaperPicker/res/values-iw/strings.xml b/WallpaperPicker/res/values-iw/strings.xml
index e29f2372f3..c6a583d0e8 100644
--- a/WallpaperPicker/res/values-iw/strings.xml
+++ b/WallpaperPicker/res/values-iw/strings.xml
@@ -22,6 +22,7 @@
"הגדר טפט""לא ניתן היה לטעון את התמונה""לא ניתן היה לטעון את התמונה כטפט"
+ "לא ניתן היה להגדיר את התמונה כטפט""%1$d נבחרו""%1$d נבחרו"
diff --git a/WallpaperPicker/res/values-ja/strings.xml b/WallpaperPicker/res/values-ja/strings.xml
index e86026bf95..f18da5d429 100644
--- a/WallpaperPicker/res/values-ja/strings.xml
+++ b/WallpaperPicker/res/values-ja/strings.xml
@@ -22,6 +22,7 @@
"壁紙を設定""画像を読み込めませんでした""画像を壁紙として読み込めませんでした"
+ "画像を壁紙として設定できませんでした""%1$d個選択済み""%1$d個選択済み"
diff --git a/WallpaperPicker/res/values-ka-rGE/strings.xml b/WallpaperPicker/res/values-ka-rGE/strings.xml
index 1ae10214a2..1f652823f7 100644
--- a/WallpaperPicker/res/values-ka-rGE/strings.xml
+++ b/WallpaperPicker/res/values-ka-rGE/strings.xml
@@ -22,6 +22,7 @@
"ფონის დაყენება""სურათი ვერ ჩაიტვირთა.""სურათი ფონად ვერ ჩაიტვირთა."
+ "სურათი ფონად ვერ დაყენდა""არჩეულია %1$d""არჩეულია %1$d"
diff --git a/WallpaperPicker/res/values-kk-rKZ/strings.xml b/WallpaperPicker/res/values-kk-rKZ/strings.xml
index a10e273189..6f4ee7ca2e 100644
--- a/WallpaperPicker/res/values-kk-rKZ/strings.xml
+++ b/WallpaperPicker/res/values-kk-rKZ/strings.xml
@@ -22,6 +22,7 @@
"Тұсқағаз орнату""Суретті жүктей алмады""Суретті артқы фон ретінде жүктей алмады"
+ "Кескінді тұсқағаз ретінде орнату мүмкін болмады""%1$d таңдалған""%1$d таңдалған"
diff --git a/WallpaperPicker/res/values-km-rKH/strings.xml b/WallpaperPicker/res/values-km-rKH/strings.xml
index 2a605e86a5..801260d3fa 100644
--- a/WallpaperPicker/res/values-km-rKH/strings.xml
+++ b/WallpaperPicker/res/values-km-rKH/strings.xml
@@ -22,6 +22,7 @@
"កំណត់ផ្ទាំងរូបភាព""មិនអាចផ្ទុករូបភាព""មិនអាចផ្ទុករូបភាពជាផ្ទាំងរូបភាព"
+ "មិនអាចកំណត់រូបភាពជាផ្ទាំងរូបភាពទេ""បានជ្រើស %1$d""បានជ្រើស %1$d"
diff --git a/WallpaperPicker/res/values-kn-rIN/strings.xml b/WallpaperPicker/res/values-kn-rIN/strings.xml
index b69bda5297..7d4d7e70e4 100644
--- a/WallpaperPicker/res/values-kn-rIN/strings.xml
+++ b/WallpaperPicker/res/values-kn-rIN/strings.xml
@@ -22,6 +22,7 @@
"ವಾಲ್ಪೇಪರ್ ಹೊಂದಿಸಿ""ಚಿತ್ರವನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ""ಚಿತ್ರವನ್ನು ವಾಲ್ಪೇಪರ್ ರೂಪದಲ್ಲಿ ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"
+ "ಚಿತ್ರವನ್ನು ವಾಲ್ಪೇಪರ್ ರೂಪದಲ್ಲಿ ಹೊಂದಿಸಲಾಗಲಿಲ್ಲ""%1$d ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ""%1$d ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"
diff --git a/WallpaperPicker/res/values-ko/strings.xml b/WallpaperPicker/res/values-ko/strings.xml
index 30e7cfa23b..f4072946da 100644
--- a/WallpaperPicker/res/values-ko/strings.xml
+++ b/WallpaperPicker/res/values-ko/strings.xml
@@ -22,6 +22,7 @@
"배경화면 설정""이미지를 로드할 수 없습니다.""이미지를 배경화면으로 로드할 수 없습니다."
+ "이미지를 배경화면으로 설정할 수 없습니다.""%1$d개 선택됨""%1$d개 선택됨"
diff --git a/WallpaperPicker/res/values-ky-rKG/strings.xml b/WallpaperPicker/res/values-ky-rKG/strings.xml
index f1ebf095d0..f53f52b29f 100644
--- a/WallpaperPicker/res/values-ky-rKG/strings.xml
+++ b/WallpaperPicker/res/values-ky-rKG/strings.xml
@@ -22,6 +22,7 @@
"Тушкагаз орнотуу""Сүрөт жүктөө мүмкүн болбоду""Сүрөттү тушкагаз катары жүктөө кыйрады"
+ "Сүрөт тушкагаз катары коюлбай койду""%1$d тандалды""%1$d тандалды"
diff --git a/WallpaperPicker/res/values-lo-rLA/strings.xml b/WallpaperPicker/res/values-lo-rLA/strings.xml
index e32cafb7d7..1da6d29c4a 100644
--- a/WallpaperPicker/res/values-lo-rLA/strings.xml
+++ b/WallpaperPicker/res/values-lo-rLA/strings.xml
@@ -22,6 +22,7 @@
"ຕັ້ງເປັນພາບພື້ນຫຼັງ""ບໍ່ສາມາດໂຫຼດຮູບໄດ້""ບໍ່ສາມາດໂຫຼດຮູບເປັນພາບພື້ນຫຼັງໄດ້"
+ "ບໍ່ສາມາດໂຫຼດຮູບເປັນພາບພື້ນຫຼັງໄດ້""ເລືອກ %1$d""ເລືອກ %1$d"
diff --git a/WallpaperPicker/res/values-lt/strings.xml b/WallpaperPicker/res/values-lt/strings.xml
index c442e3a5ad..98c9a365c4 100644
--- a/WallpaperPicker/res/values-lt/strings.xml
+++ b/WallpaperPicker/res/values-lt/strings.xml
@@ -22,6 +22,7 @@
"Nustatyti ekrano foną""Nepavyko įkelti vaizdo""Nepavyko įkelti vaizdo kaip ekrano fono"
+ "Nepavyko nustatyti vaizdo kaip ekrano fono""Pasirinkta: %1$d""Pasirinkta: %1$d"
diff --git a/WallpaperPicker/res/values-lv/strings.xml b/WallpaperPicker/res/values-lv/strings.xml
index d05a175470..ff7876c72e 100644
--- a/WallpaperPicker/res/values-lv/strings.xml
+++ b/WallpaperPicker/res/values-lv/strings.xml
@@ -22,6 +22,7 @@
"Iestatīt fona tapeti""Nevarēja ielādēt attēlu.""Nevarēja ielādēt attēlu kā fona tapeti."
+ "Nevarēja iestatīt attēlu kā fona tapeti.""Atlasīts: %1$d""Atlasīta: %1$d"
diff --git a/WallpaperPicker/res/values-mk-rMK/strings.xml b/WallpaperPicker/res/values-mk-rMK/strings.xml
index f9e0963ed0..13b38cd1d5 100644
--- a/WallpaperPicker/res/values-mk-rMK/strings.xml
+++ b/WallpaperPicker/res/values-mk-rMK/strings.xml
@@ -22,6 +22,7 @@
"Подеси тапет""Сликата не можеше да се вчита""Сликата не можеше да се вчита како тапет"
+ "Сликата не може да се постави како тапет""Избрано %1$d""Избрано %1$d"
diff --git a/WallpaperPicker/res/values-ml-rIN/strings.xml b/WallpaperPicker/res/values-ml-rIN/strings.xml
index 6be771c0ed..5831b36b91 100644
--- a/WallpaperPicker/res/values-ml-rIN/strings.xml
+++ b/WallpaperPicker/res/values-ml-rIN/strings.xml
@@ -22,6 +22,7 @@
"വാൾപേപ്പർ സജ്ജീകരിക്കുക""ചിത്രം ലോഡുചെയ്യാനായില്ല""വാൾപേപ്പറായി ചിത്രം ലോഡുചെയ്യാനായില്ല"
+ "വാൾപേപ്പറായി ചിത്രം ലോഡുചെയ്യാനായില്ല""%1$d തിരഞ്ഞെടുത്തു""%1$d തിരഞ്ഞെടുത്തു"
diff --git a/WallpaperPicker/res/values-mn-rMN/strings.xml b/WallpaperPicker/res/values-mn-rMN/strings.xml
index b88da5a96e..9995547d55 100644
--- a/WallpaperPicker/res/values-mn-rMN/strings.xml
+++ b/WallpaperPicker/res/values-mn-rMN/strings.xml
@@ -22,6 +22,7 @@
"Ханын зургийг тохируулах""Зургийг ачаалж чадсангүй""Зургийг ханын зураг болгож ачаалж чадсангүй"
+ "Зургийг ханын зураг болгож чадсангүй""%1$d сонгогдсон""%1$d сонгогдсон"
diff --git a/WallpaperPicker/res/values-mr-rIN/strings.xml b/WallpaperPicker/res/values-mr-rIN/strings.xml
index 128b21c1ca..d740fd29e6 100644
--- a/WallpaperPicker/res/values-mr-rIN/strings.xml
+++ b/WallpaperPicker/res/values-mr-rIN/strings.xml
@@ -22,6 +22,7 @@
"वॉलपेपर सेट करा""प्रतिमा लोड करू शकलो नाही""वॉलपेपर म्हणून प्रतिमा लोड करू शकलो नाही"
+ "प्रतिमा वॉलपेपर म्हणून सेट करू शकलो नाही""%1$d निवडले""%1$d निवडले"
diff --git a/WallpaperPicker/res/values-ms-rMY/strings.xml b/WallpaperPicker/res/values-ms-rMY/strings.xml
index 9d824e4fa3..759e48cecc 100644
--- a/WallpaperPicker/res/values-ms-rMY/strings.xml
+++ b/WallpaperPicker/res/values-ms-rMY/strings.xml
@@ -22,6 +22,7 @@
"Tetapkan kertas dinding""Tidak dapat memuatkan imej""Tidak dapat memuatkan imej sebagai kertas dinding"
+ "Tidak dapat menetapkan imej sebagai kertas dinding""%1$d dipilih""%1$d dipilih"
diff --git a/WallpaperPicker/res/values-my-rMM/strings.xml b/WallpaperPicker/res/values-my-rMM/strings.xml
index 233611431d..5197b98ea3 100644
--- a/WallpaperPicker/res/values-my-rMM/strings.xml
+++ b/WallpaperPicker/res/values-my-rMM/strings.xml
@@ -22,6 +22,7 @@
"နောက်ခံအား သတ်မှတ်ရန်""ပုံရိပ် တင် မရပါ""ပုံရိပ်အား နောက်ခံအဖြစ် တင် မရပါ"
+ "ပုံရိပ်အား နောက်ခံအဖြစ် တင်၍မရပါ""%1$d ရွေးချယ်ပြီး""%1$d ရွေးချယ်ပြီး"
diff --git a/WallpaperPicker/res/values-nb/strings.xml b/WallpaperPicker/res/values-nb/strings.xml
index 9ae0b989a7..8125b53bf0 100644
--- a/WallpaperPicker/res/values-nb/strings.xml
+++ b/WallpaperPicker/res/values-nb/strings.xml
@@ -22,6 +22,7 @@
"Angi bakgrunn""Kunne ikke laste inn bildet""Kunne ikke laste inn bildet som bakgrunn"
+ "Kunne ikke angi bildet som bakgrunn""%1$d valgt""%1$d valgt"
diff --git a/WallpaperPicker/res/values-ne-rNP/strings.xml b/WallpaperPicker/res/values-ne-rNP/strings.xml
index 5bca8d809d..b77a1c5d7b 100644
--- a/WallpaperPicker/res/values-ne-rNP/strings.xml
+++ b/WallpaperPicker/res/values-ne-rNP/strings.xml
@@ -22,6 +22,7 @@
"वालपेपर मिलाउनुहोस्""तस्बिर लोड गर्न सकिएन""तस्बिरलाई वालपेपरका रूपमा लोड गर्न सकिएन"
+ "छविलाई वालपेपरको रूपमा सेट गर्न सकिएन""%1$d चयन भयो""%1$d चयन भयो"
diff --git a/WallpaperPicker/res/values-nl/strings.xml b/WallpaperPicker/res/values-nl/strings.xml
index a86f0f6518..dc78305aa3 100644
--- a/WallpaperPicker/res/values-nl/strings.xml
+++ b/WallpaperPicker/res/values-nl/strings.xml
@@ -22,6 +22,7 @@
"Achtergrond instellen""Kan afbeelding niet laden""Kan afbeelding niet laden als achtergrond"
+ "Kan afbeelding niet instellen als achtergrond""%1$d geselecteerd""%1$d geselecteerd"
diff --git a/WallpaperPicker/res/values-pl/strings.xml b/WallpaperPicker/res/values-pl/strings.xml
index cd52082d6b..9693de46c7 100644
--- a/WallpaperPicker/res/values-pl/strings.xml
+++ b/WallpaperPicker/res/values-pl/strings.xml
@@ -22,6 +22,7 @@
"Ustaw tapetę""Nie udało się załadować obrazu""Nie udało się załadować obrazu jako tapety"
+ "Nie udało się ustawić obrazu jako tapety""Wybranych %1$d""Wybrana %1$d"
diff --git a/WallpaperPicker/res/values-pt-rPT/strings.xml b/WallpaperPicker/res/values-pt-rPT/strings.xml
index 82aa4694a1..3c4fa9b731 100644
--- a/WallpaperPicker/res/values-pt-rPT/strings.xml
+++ b/WallpaperPicker/res/values-pt-rPT/strings.xml
@@ -22,6 +22,7 @@
"Definir imagem fundo""Não foi possível carregar a imagem""Não foi possível carregar a imagem como imagem de fundo"
+ "Não foi possível definir a imagem como imagem de fundo""%1$d selecionadas""%1$d selecionada"
diff --git a/WallpaperPicker/res/values-pt/strings.xml b/WallpaperPicker/res/values-pt/strings.xml
index 74ff3106c3..2520eed3a7 100644
--- a/WallpaperPicker/res/values-pt/strings.xml
+++ b/WallpaperPicker/res/values-pt/strings.xml
@@ -22,6 +22,7 @@
"Definir plano de fundo""Não foi possível carregar a imagem""Não foi possível carregar a imagem como plano de fundo"
+ "Não foi possível definir a imagem como plano de fundo""%1$d selecionados""%1$d selecionado"
diff --git a/WallpaperPicker/res/values-ro/strings.xml b/WallpaperPicker/res/values-ro/strings.xml
index 6281b80fc3..f5df3eee22 100644
--- a/WallpaperPicker/res/values-ro/strings.xml
+++ b/WallpaperPicker/res/values-ro/strings.xml
@@ -22,6 +22,7 @@
"Setați imaginea de fundal""Nu s-a putut încărca imaginea""Nu s-a putut încărca imaginea ca fundal"
+ "Nu s-a putut seta ca imagine de fundal""%1$d selectate""%1$d selectată"
diff --git a/WallpaperPicker/res/values-ru/strings.xml b/WallpaperPicker/res/values-ru/strings.xml
index 959205a336..f8c350a1f8 100644
--- a/WallpaperPicker/res/values-ru/strings.xml
+++ b/WallpaperPicker/res/values-ru/strings.xml
@@ -22,6 +22,7 @@
"Установить как обои""Не удалось загрузить изображение""Не удалось загрузить изображение"
+ "Не удалось сменить обои""Выбрано: %1$d""Выбрано: %1$d"
diff --git a/WallpaperPicker/res/values-si-rLK/strings.xml b/WallpaperPicker/res/values-si-rLK/strings.xml
index 37ce1fc528..3945bdf89d 100644
--- a/WallpaperPicker/res/values-si-rLK/strings.xml
+++ b/WallpaperPicker/res/values-si-rLK/strings.xml
@@ -22,6 +22,7 @@
"වෝල්පේපරය සකසන්න""පින්තූරය පූරණය කිරීමට නොහැකි විය""පින්තූරය වෝල්පේපරයක් ලෙස පූරණය කිරීමට නොහැකි විය"
+ "පින්තූරය බිතුපතක් ලෙස සැකසීමට නොහැකි විය""%1$d තෝරා ගන්නා ලදි""%1$d තෝරා ගන්නා ලදි"
diff --git a/WallpaperPicker/res/values-sk/strings.xml b/WallpaperPicker/res/values-sk/strings.xml
index 9d38de437b..fb2c819b27 100644
--- a/WallpaperPicker/res/values-sk/strings.xml
+++ b/WallpaperPicker/res/values-sk/strings.xml
@@ -22,6 +22,7 @@
"Nastaviť tapetu""Obrázok nie je možné načítať""Obrázok nie je možné načítať ako tapetu"
+ "Obrázok nie je možné nastaviť ako tapetu""Počet vybratých položiek: %1$d""Počet vybratých položiek: %1$d"
diff --git a/WallpaperPicker/res/values-sl/strings.xml b/WallpaperPicker/res/values-sl/strings.xml
index e776cc7d54..a7ff0891d9 100644
--- a/WallpaperPicker/res/values-sl/strings.xml
+++ b/WallpaperPicker/res/values-sl/strings.xml
@@ -22,6 +22,7 @@
"Nastavi ozadje""Slike ni bilo mogoče naložiti""Slike ni bilo mogoče naložiti kot ozadje"
+ "Slike ni bilo mogoče nastaviti kot ozadje""Št. izbranih: %1$d""Št. izbranih: %1$d"
diff --git a/WallpaperPicker/res/values-sr/strings.xml b/WallpaperPicker/res/values-sr/strings.xml
index c37bc060f5..6154526cda 100644
--- a/WallpaperPicker/res/values-sr/strings.xml
+++ b/WallpaperPicker/res/values-sr/strings.xml
@@ -22,6 +22,7 @@
"Подеси позадину""Није могуће учитати слику""Није могуће учитати слику као позадину"
+ "Учитавање слике као позадине није успело""Изабрано је %1$d""Изабрана је %1$d"
diff --git a/WallpaperPicker/res/values-sv/strings.xml b/WallpaperPicker/res/values-sv/strings.xml
index 044508b4da..38062b9eed 100644
--- a/WallpaperPicker/res/values-sv/strings.xml
+++ b/WallpaperPicker/res/values-sv/strings.xml
@@ -22,6 +22,7 @@
"Ange bakgrund""Det gick inte att läsa in bilden""Det gick inte att läsa in bilden som bakgrund"
+ "Det gick inte att ange bilden som bakgrund""%1$d har valts""%1$d har valts"
diff --git a/WallpaperPicker/res/values-sw/strings.xml b/WallpaperPicker/res/values-sw/strings.xml
index 367912aab7..729a79a74f 100644
--- a/WallpaperPicker/res/values-sw/strings.xml
+++ b/WallpaperPicker/res/values-sw/strings.xml
@@ -22,6 +22,7 @@
"Weka mandhari""Haikuweza kupakia picha""Haikuweza kupakia picha iwe mandhari"
+ "Haikuweza kuweka picha kuwa mandhari""%1$d zimechaguliwa""%1$d zimechaguliwa"
diff --git a/WallpaperPicker/res/values-sw720dp-v19/styles.xml b/WallpaperPicker/res/values-sw720dp-v19/styles.xml
deleted file mode 100644
index d8dab223a1..0000000000
--- a/WallpaperPicker/res/values-sw720dp-v19/styles.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
diff --git a/WallpaperPicker/res/values-sw720dp/styles.xml b/WallpaperPicker/res/values-sw720dp/styles.xml
index 12f8884f18..0058f7e38c 100644
--- a/WallpaperPicker/res/values-sw720dp/styles.xml
+++ b/WallpaperPicker/res/values-sw720dp/styles.xml
@@ -18,7 +18,11 @@
-->
-
diff --git a/WallpaperPicker/res/values-ta-rIN/strings.xml b/WallpaperPicker/res/values-ta-rIN/strings.xml
index 7284da8ae5..69a9d386be 100644
--- a/WallpaperPicker/res/values-ta-rIN/strings.xml
+++ b/WallpaperPicker/res/values-ta-rIN/strings.xml
@@ -22,6 +22,7 @@
"வால்பேப்பரை அமை""படத்தை ஏற்ற முடியவில்லை""படத்தை வால்பேப்பராக ஏற்ற முடியவில்லை"
+ "படத்தை வால்பேப்பராக அமைக்க முடியவில்லை""%1$d தேர்ந்தெடுக்கப்பட்டன""%1$d தேர்ந்தெடுக்கப்பட்டது"
diff --git a/WallpaperPicker/res/values-te-rIN/strings.xml b/WallpaperPicker/res/values-te-rIN/strings.xml
index 4c5ad2f080..6fb5fa29b1 100644
--- a/WallpaperPicker/res/values-te-rIN/strings.xml
+++ b/WallpaperPicker/res/values-te-rIN/strings.xml
@@ -22,6 +22,7 @@
"వాల్పేపర్ను సెట్ చేయి""చిత్రాన్ని లోడ్ చేయడం సాధ్యపడలేదు""చిత్రాన్ని వాల్పేపర్గా లోడ్ చేయడం సాధ్యపడలేదు"
+ "చిత్రాన్ని వాల్పేపర్గా సెట్ చేయడం సాధ్యపడలేదు""%1$d ఎంచుకోబడింది""%1$d ఎంచుకోబడింది"
diff --git a/WallpaperPicker/res/values-th/strings.xml b/WallpaperPicker/res/values-th/strings.xml
index b3d7a8bc7e..c689436060 100644
--- a/WallpaperPicker/res/values-th/strings.xml
+++ b/WallpaperPicker/res/values-th/strings.xml
@@ -22,6 +22,7 @@
"ตั้งวอลเปเปอร์""ไม่สามารถโหลดรูปภาพ""ไม่สามารถโหลดรูปภาพเป็นวอลเปเปอร์"
+ "ไม่สามารถตั้งรูปภาพเป็นวอลเปเปอร์""เลือกไว้ %1$d""เลือกไว้ %1$d"
diff --git a/WallpaperPicker/res/values-tl/strings.xml b/WallpaperPicker/res/values-tl/strings.xml
index db8f4d5426..c760d7f6e3 100644
--- a/WallpaperPicker/res/values-tl/strings.xml
+++ b/WallpaperPicker/res/values-tl/strings.xml
@@ -22,6 +22,7 @@
"Itakda ang wallpaper""Hindi ma-load ang larawan""Hindi ma-load ang larawan bilang wallpaper"
+ "Hindi maitakda ang larawan bilang wallpaper""%1$d ang napili""%1$d ang napili"
diff --git a/WallpaperPicker/res/values-tr/strings.xml b/WallpaperPicker/res/values-tr/strings.xml
index d1e57edbbf..e9dc1d2b29 100644
--- a/WallpaperPicker/res/values-tr/strings.xml
+++ b/WallpaperPicker/res/values-tr/strings.xml
@@ -22,6 +22,7 @@
"Duvar kağıdını ayarla""Resim yüklenemedi""Resim duvar kağıdı olarak yüklenemedi"
+ "Resim, duvar kağıdı olarak ayarlanamadı""%1$d tane seçildi""%1$d tane seçildi"
diff --git a/WallpaperPicker/res/values-uk/strings.xml b/WallpaperPicker/res/values-uk/strings.xml
index 756e7cf092..c6669c0c86 100644
--- a/WallpaperPicker/res/values-uk/strings.xml
+++ b/WallpaperPicker/res/values-uk/strings.xml
@@ -22,6 +22,7 @@
"Установити фон""Не вдалося завантажити зображення""Не вдалося завантажити зображення як фоновий малюнок"
+ "Не вдалося зробити зображення фоновим малюнком""Вибрано %1$d""Вибрано %1$d"
diff --git a/WallpaperPicker/res/values-ur-rPK/strings.xml b/WallpaperPicker/res/values-ur-rPK/strings.xml
index 86d8163d90..e240e1a50a 100644
--- a/WallpaperPicker/res/values-ur-rPK/strings.xml
+++ b/WallpaperPicker/res/values-ur-rPK/strings.xml
@@ -22,6 +22,7 @@
"وال پیپر سیٹ کریں""تصویر کو لوڈ نہیں کیا جا سکا""تصویر کو وال پیپر کے بطور لوڈ نہیں کیا جا سکا"
+ "تصویر کو بطور وال پیپر سیٹ نہیں کیا جا سکا""%1$d کو منتخب کیا گیا""%1$d کو منتخب کیا گیا"
diff --git a/WallpaperPicker/res/values-uz-rUZ/strings.xml b/WallpaperPicker/res/values-uz-rUZ/strings.xml
index 97d3938ae1..5a79981e9d 100644
--- a/WallpaperPicker/res/values-uz-rUZ/strings.xml
+++ b/WallpaperPicker/res/values-uz-rUZ/strings.xml
@@ -22,6 +22,7 @@
"Fonga rasm o‘rnatish""Rasm yuklanmadi""Fon rasmi sifatida rasm yuklanmadi"
+ "Rasmni fon rasmi sifatida o‘rnatib bo‘lmadi""%1$d ta tanlandi""%1$d ta tanlandi"
diff --git a/WallpaperPicker/res/values-v21/styles.xml b/WallpaperPicker/res/values-v21/styles.xml
index 70220edb9b..d2f7dd44ff 100644
--- a/WallpaperPicker/res/values-v21/styles.xml
+++ b/WallpaperPicker/res/values-v21/styles.xml
@@ -34,8 +34,11 @@
diff --git a/res/xml/default_workspace_3x3.xml b/res/xml/default_workspace_3x3.xml
new file mode 100644
index 0000000000..8d99404504
--- /dev/null
+++ b/res/xml/default_workspace_3x3.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/default_workspace_4x4.xml b/res/xml/default_workspace_4x4.xml
index 060a1f8802..979a1b4c84 100644
--- a/res/xml/default_workspace_4x4.xml
+++ b/res/xml/default_workspace_4x4.xml
@@ -23,7 +23,7 @@
+ launcher:y="-1" >
@@ -31,7 +31,7 @@
+ launcher:y="-1" >
@@ -39,7 +39,7 @@
+ launcher:y="-1" >
diff --git a/res/xml/default_workspace_5x5.xml b/res/xml/default_workspace_5x5.xml
index 3226617209..f9cc0e789b 100644
--- a/res/xml/default_workspace_5x5.xml
+++ b/res/xml/default_workspace_5x5.xml
@@ -23,7 +23,7 @@
+ launcher:y="-1" >
@@ -32,7 +32,7 @@
+ launcher:y="-1" >
@@ -41,7 +41,7 @@
+ launcher:y="-1" >
diff --git a/res/xml/default_workspace_5x6.xml b/res/xml/default_workspace_5x6.xml
index bc236fb142..8493c265e2 100644
--- a/res/xml/default_workspace_5x6.xml
+++ b/res/xml/default_workspace_5x6.xml
@@ -23,13 +23,13 @@
+ launcher:x="-1"
+ launcher:y="-1" >
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 3b25dca34c..c4315936cc 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -22,6 +22,8 @@ import android.content.Context;
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 java.util.ArrayList;
import java.util.HashSet;
@@ -118,6 +120,21 @@ class AllAppsList {
}
}
+ /**
+ * Updates the apps for the given packageName and user based on {@param op}.
+ */
+ public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) {
+ final List 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())) {
+ info.isDisabled = op.apply(info.isDisabled);
+ modified.add(info);
+ }
+ }
+ }
+
public void updateIconsAndLabels(HashSet packages, UserHandleCompat user,
ArrayList outUpdates) {
for (AppInfo info : data) {
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index c95d5585a7..c7a6ae4291 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -26,6 +26,7 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
import java.util.Arrays;
@@ -51,11 +52,6 @@ public class AppInfo extends ItemInfo {
*/
boolean usingLowResIcon;
- /**
- * The time at which the app was first installed.
- */
- long firstInstallTime;
-
public ComponentName componentName;
static final int DOWNLOADED_FLAG = 1;
@@ -63,6 +59,11 @@ public class AppInfo extends ItemInfo {
int flags = 0;
+ /**
+ * {@see ShortcutInfo#isDisabled}
+ */
+ int isDisabled = ShortcutInfo.DEFAULT;
+
AppInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
}
@@ -80,11 +81,22 @@ public class AppInfo extends ItemInfo {
*/
public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
IconCache iconCache) {
+ this(context, info, user, iconCache,
+ UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
+ }
+
+ public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
+ IconCache iconCache, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = ItemInfo.NO_ID;
-
flags = initFlags(info);
- firstInstallTime = info.getFirstInstallTime();
+ if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
+ isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ }
+ if (quietModeEnabled) {
+ isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
+ }
+
iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */);
intent = makeLaunchIntent(context, info, user);
this.user = user;
@@ -109,7 +121,7 @@ public class AppInfo extends ItemInfo {
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
flags = info.flags;
- firstInstallTime = info.firstInstallTime;
+ isDisabled = info.isDisabled;
iconBitmap = info.iconBitmap;
}
@@ -129,7 +141,6 @@ public class AppInfo extends ItemInfo {
Log.d(tag, label + " size=" + list.size());
for (AppInfo info: list) {
Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
- + " firstInstallTime=" + info.firstInstallTime
+ " componentName=" + info.componentName.getPackageName());
}
}
@@ -151,4 +162,9 @@ public class AppInfo extends ItemInfo {
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
.putExtra(EXTRA_PROFILE, serialNumber);
}
+
+ @Override
+ public boolean isDisabled() {
+ return isDisabled != 0;
+ }
}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index e6bf525315..6b7ff886c1 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -11,10 +11,15 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
-public class AppWidgetResizeFrame extends FrameLayout {
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.util.FocusLogic;
+
+public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListener {
private static final int SNAP_DURATION = 150;
private static final float DIMMED_HANDLE_ALPHA = 0f;
private static final float RESIZE_THRESHOLD = 0.66f;
@@ -40,6 +45,8 @@ public class AppWidgetResizeFrame extends FrameLayout {
private final int[] mLastDirectionVector = new int[2];
private final int[] mTmpPt = new int[2];
+ private final DragViewStateAnnouncer mStateAnnouncer;
+
private boolean mLeftBorderActive;
private boolean mRightBorderActive;
private boolean mTopBorderActive;
@@ -78,6 +85,8 @@ public class AppWidgetResizeFrame extends FrameLayout {
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);
@@ -137,6 +146,8 @@ public class AppWidgetResizeFrame extends FrameLayout {
// cells (same if not resized, or different) will be marked as occupied when the resize
// frame is dismissed.
mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
+
+ setOnKeyListener(this);
}
public boolean beginResizeIfPointInRegion(int x, int y) {
@@ -320,12 +331,18 @@ public class AppWidgetResizeFrame extends FrameLayout {
if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
mDirectionVector, onDismiss)) {
+ if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
+ mStateAnnouncer.announce(
+ mLauncher.getString(R.string.widget_resized, spanX, spanY));
+ }
+
lp.tmpCellX = cellX;
lp.tmpCellY = cellY;
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
mRunningVInc += vSpanDelta;
mRunningHInc += hSpanDelta;
+
if (!onDismiss) {
updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
}
@@ -462,5 +479,19 @@ public class AppWidgetResizeFrame extends FrameLayout {
set.setDuration(SNAP_DURATION);
set.start();
}
+
+ setFocusableInTouchMode(true);
+ requestFocus();
+ }
+
+ @Override
+ 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();
+ mWidgetView.requestFocus();
+ return true;
+ }
+ return false;
}
}
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index b1d51ece04..54ce0fd3cc 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -13,7 +13,6 @@ import android.os.AsyncTask;
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
import java.util.ArrayList;
import java.util.List;
@@ -90,7 +89,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
}
return null;
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
}
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 440e4e7b99..0d71a0c6cf 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -125,8 +125,12 @@ public class AutoInstallsLayout {
private static final String ATTR_CLASS_NAME = "className";
private static final String ATTR_TITLE = "title";
private static final String ATTR_SCREEN = "screen";
+
+ // x and y can be specified as negative integers, in which case -1 represents the
+ // last row / column, -2 represents the second last, and so on.
private static final String ATTR_X = "x";
private static final String ATTR_Y = "y";
+
private static final String ATTR_SPAN_X = "spanX";
private static final String ATTR_SPAN_Y = "spanY";
private static final String ATTR_ICON = "icon";
@@ -154,6 +158,8 @@ public class AutoInstallsLayout {
protected final int mLayoutId;
private final int mHotseatAllAppsRank;
+ private final int mRowCount;
+ private final int mColumnCount;
private final long[] mTemp = new long[2];
@Thunk final ContentValues mValues;
@@ -164,13 +170,6 @@ public class AutoInstallsLayout {
public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
LayoutParserCallback callback, Resources res,
int layoutId, String rootTag) {
- this(context, appWidgetHost, callback, res, layoutId, rootTag,
- LauncherAppState.getInstance().getInvariantDeviceProfile().hotseatAllAppsRank);
- }
-
- public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
- LayoutParserCallback callback, Resources res,
- int layoutId, String rootTag, int hotseatAllAppsRank) {
mContext = context;
mAppWidgetHost = appWidgetHost;
mCallback = callback;
@@ -181,7 +180,11 @@ public class AutoInstallsLayout {
mSourceRes = res;
mLayoutId = layoutId;
- mHotseatAllAppsRank = hotseatAllAppsRank;
+
+ InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ mHotseatAllAppsRank = idp.hotseatAllAppsRank;
+ mRowCount = idp.numRows;
+ mColumnCount = idp.numColumns;
}
/**
@@ -261,8 +264,11 @@ public class AutoInstallsLayout {
mValues.put(Favorites.CONTAINER, container);
mValues.put(Favorites.SCREEN, screenId);
- mValues.put(Favorites.CELLX, getAttributeValue(parser, ATTR_X));
- mValues.put(Favorites.CELLY, getAttributeValue(parser, ATTR_Y));
+
+ mValues.put(Favorites.CELLX,
+ convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
+ mValues.put(Favorites.CELLY,
+ convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
TagParser tagParser = tagParserMap.get(parser.getName());
if (tagParser == null) {
@@ -648,6 +654,16 @@ public class AutoInstallsLayout {
}
}
+ private static String convertToDistanceFromEnd(String value, int endValue) {
+ if (!TextUtils.isEmpty(value)) {
+ int x = Integer.parseInt(value);
+ if (x < 0) {
+ return Integer.toString(endValue + x);
+ }
+ }
+ return value;
+ }
+
/**
* Return attribute value, attempting launcher-specific namespace first
* before falling back to anonymous attribute.
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c11824054c..538c24a66b 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -17,32 +17,34 @@
package com.android.launcher3;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.LinearLayout;
+import android.view.View;
+import android.widget.FrameLayout;
/**
* A base container view, which supports resizing.
*/
-public abstract class BaseContainerView extends LinearLayout implements Insettable {
+public abstract class BaseContainerView extends FrameLayout implements Insettable {
private final static String TAG = "BaseContainerView";
// The window insets
- private Rect mInsets = new Rect();
- // The bounds of the search bar. Only the left, top, right are used to inset the
- // search bar and the height is determined by the measurement of the layout
- private Rect mFixedSearchBarBounds = new Rect();
- // The computed bounds of the search bar
- private Rect mSearchBarBounds = new Rect();
- // The computed bounds of the container
- protected Rect mContentBounds = new Rect();
+ private final Rect mInsets = new Rect();
// The computed padding to apply to the container to achieve the container bounds
- private Rect mContentPadding = new Rect();
+ protected final Rect mContentPadding = new Rect();
// The inset to apply to the edges and between the search bar and the container
- private int mContainerBoundsInset;
- private boolean mHasSearchBar;
+ private final int mContainerBoundsInset;
+
+ private final Drawable mRevealDrawable;
+
+ private View mRevealView;
+ private View mContent;
+
+ protected final int mHorizontalPadding;
public BaseContainerView(Context context) {
this(context, null);
@@ -55,6 +57,30 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContainerBoundsInset = getResources().getDimensionPixelSize(R.dimen.container_bounds_inset);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.BaseContainerView, defStyleAttr, 0);
+ mRevealDrawable = a.getDrawable(R.styleable.BaseContainerView_revealBackground);
+ a.recycle();
+
+ int maxSize = getResources().getDimensionPixelSize(R.dimen.container_max_width);
+ int minMargin = getResources().getDimensionPixelSize(R.dimen.container_min_margin);
+ int width = ((Launcher) context).getDeviceProfile().availableWidthPx;
+
+ if (maxSize > 0) {
+ mHorizontalPadding = Math.max(minMargin, (width - maxSize) / 2);
+ } else {
+ mHorizontalPadding = Math.max(minMargin,
+ (int) getResources().getFraction(R.fraction.container_margin, width, 1));
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mContent = findViewById(R.id.main_content);
+ mRevealView = findViewById(R.id.reveal_view);
}
@Override
@@ -63,20 +89,10 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
updateBackgroundAndPaddings();
}
- protected void setHasSearchBar() {
- mHasSearchBar = true;
- }
-
/**
* Sets the search bar bounds for this container view to match.
*/
final public void setSearchBarBounds(Rect bounds) {
- if (LauncherAppState.isDogfoodBuild() && !isValidSearchBarBounds(bounds)) {
- Log.e(TAG, "Invalid search bar bounds: " + bounds);
- }
-
- mFixedSearchBarBounds.set(bounds);
-
// Post the updates since they can trigger a relayout, and this call can be triggered from
// a layout pass itself.
post(new Runnable() {
@@ -92,54 +108,46 @@ public abstract class BaseContainerView extends LinearLayout implements Insettab
*/
protected void updateBackgroundAndPaddings() {
Rect padding;
- Rect searchBarBounds = new Rect();
- if (!isValidSearchBarBounds(mFixedSearchBarBounds)) {
- // Use the default bounds
- padding = new Rect(mInsets.left + mContainerBoundsInset,
- (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
- mInsets.right + mContainerBoundsInset,
- mInsets.bottom + mContainerBoundsInset);
+ padding = new Rect(
+ mHorizontalPadding,
+ mInsets.top + mContainerBoundsInset,
+ mHorizontalPadding,
+ mInsets.bottom + mContainerBoundsInset
+ );
- // Special case -- we have the search bar, but no specific bounds, so just give it
- // the inset bounds without a height.
- searchBarBounds.set(mInsets.left + mContainerBoundsInset,
- mInsets.top + mContainerBoundsInset,
- getMeasuredWidth() - (mInsets.right + mContainerBoundsInset), 0);
- } else {
- // Use the search bounds, if there is a search bar, the bounds will contain
- // the offsets for the insets so we can ignore that
- padding = new Rect(mFixedSearchBarBounds.left,
- (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
- getMeasuredWidth() - mFixedSearchBarBounds.right,
- mInsets.bottom + mContainerBoundsInset);
-
- // Use the search bounds
- searchBarBounds.set(mFixedSearchBarBounds);
- }
-
- // If either the computed container padding has changed, or the computed search bar bounds
- // has changed, then notify the container
- if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
+ // The container padding changed, notify the container.
+ if (!padding.equals(mContentPadding)) {
mContentPadding.set(padding);
- mContentBounds.set(padding.left, padding.top,
- getMeasuredWidth() - padding.right,
- getMeasuredHeight() - padding.bottom);
- mSearchBarBounds.set(searchBarBounds);
- onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
+ onUpdateBackgroundAndPaddings(padding);
}
}
- /**
- * To be implemented by container views to update themselves when the bounds changes.
- */
- protected abstract void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding);
+ private void onUpdateBackgroundAndPaddings(Rect padding) {
+ // Apply the top-bottom padding to itself so that the launcher transition is
+ // clipped correctly
+ setPadding(0, padding.top, 0, padding.bottom);
- /**
- * Returns whether the search bar bounds we got are considered valid.
- */
- private boolean isValidSearchBarBounds(Rect searchBarBounds) {
- return !searchBarBounds.isEmpty() &&
- searchBarBounds.right <= getMeasuredWidth() &&
- searchBarBounds.bottom <= getMeasuredHeight();
+ InsetDrawable background = new InsetDrawable(mRevealDrawable,
+ padding.left, 0, padding.right, 0);
+ mRevealView.setBackground(background.getConstantState().newDrawable());
+ mContent.setBackground(background);
+
+ // We let the content have a intent background, but still have full width.
+ // This allows the scroll bar to be used responsive outside the background bounds as well.
+ mContent.setPadding(0, 0, 0, 0);
+
+ Rect bgPadding = new Rect();
+ background.getPadding(bgPadding);
+ onUpdateBgPadding(padding, bgPadding);
+ }
+
+ protected abstract void onUpdateBgPadding(Rect padding, Rect bgPadding);
+
+ public final View getContentView() {
+ return mContent;
+ }
+
+ public final View getRevealView() {
+ return mRevealView;
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index f0d8b3b3d2..f8ef1e1560 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -52,8 +52,8 @@ public abstract class BaseRecyclerView extends RecyclerView
public int rowIndex;
// The offset of the first visible row
public int rowTopOffset;
- // The height of a given row (they are currently all the same height)
- public int rowHeight;
+ // The adapter position of the first visible item
+ public int itemPos;
}
protected BaseRecyclerViewFastScrollBar mScrollbar;
@@ -186,16 +186,22 @@ public abstract class BaseRecyclerView extends RecyclerView
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;
+ }
+
/**
* Returns the available scroll height:
* AvailableScrollHeight = Total height of the all items - last page height
- *
- * This assumes that all rows are the same height.
*/
- protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
- int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
- int availableScrollHeight = scrollHeight - visibleHeight;
+ protected int getAvailableScrollHeight(int rowCount) {
+ int totalHeight = getPaddingTop() + getTop(rowCount) + getPaddingBottom();
+ int availableScrollHeight = totalHeight - getVisibleHeight();
return availableScrollHeight;
}
@@ -204,8 +210,7 @@ public abstract class BaseRecyclerView extends RecyclerView
* AvailableScrollBarHeight = Total height of the visible view - thumb height
*/
protected int getAvailableScrollBarHeight() {
- int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
- int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
+ int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
return availableScrollBarHeight;
}
@@ -223,6 +228,13 @@ public abstract class BaseRecyclerView extends RecyclerView
return defaultInactiveThumbColor;
}
+ /**
+ * Returns the scrollbar for this recycler view.
+ */
+ public BaseRecyclerViewFastScrollBar getScrollBar() {
+ return mScrollbar;
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -243,7 +255,7 @@ public abstract class BaseRecyclerView extends RecyclerView
int rowCount) {
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
- int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
+ int availableScrollHeight = getAvailableScrollHeight(rowCount);
if (availableScrollHeight <= 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
@@ -252,8 +264,7 @@ public abstract class BaseRecyclerView extends RecyclerView
// 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 scrollY = getPaddingTop() +
- (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
+ int scrollY = getScrollTop(scrollPosState);
int scrollBarY = mBackgroundPadding.top +
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -267,25 +278,48 @@ public abstract class BaseRecyclerView extends RecyclerView
mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
}
+ /**
+ * @return whether fast scrolling is supported in the current state.
+ */
+ protected boolean supportsFastScrolling() {
+ return true;
+ }
+
+ /**
+ * Maps the touch (from 0..1) to the adapter position that should be visible.
+ *
Override in each subclass of this base class.
+ *
+ * @return the scroll top of this recycler view.
+ */
+ protected int getScrollTop(ScrollPositionState scrollPosState) {
+ return getPaddingTop() + getTop(scrollPosState.rowIndex) -
+ scrollPosState.rowTopOffset;
+ }
+
+ /**
+ * Returns information about the item that the recycler view is currently scrolled to.
+ */
+ protected abstract void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask);
+
+ /**
+ * Returns the top (or y position) of the row at the specified index.
+ */
+ protected abstract int getTop(int rowIndex);
+
/**
* Maps the touch (from 0..1) to the adapter position that should be visible.
*
Override in each subclass of this base class.
*/
- public abstract String scrollToPositionAtProgress(float touchFraction);
+ protected abstract String scrollToPositionAtProgress(float touchFraction);
/**
* Updates the bounds for the scrollbar.
*
Override in each subclass of this base class.
*/
- public abstract void onUpdateScrollbar(int dy);
+ protected abstract void onUpdateScrollbar(int dy);
/**
*
Override in each subclass of this base class.
*/
- public void onFastScrollCompleted() {}
-
- /**
- * Returns information about the item that the recycler view is currently scrolled to.
- */
- protected abstract void getCurScrollState(ScrollPositionState stateOut);
+ protected void onFastScrollCompleted() {}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index fcee7e8ddf..a6801696a6 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -27,6 +27,7 @@ import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.launcher3.util.Thunk;
@@ -37,7 +38,7 @@ import com.android.launcher3.util.Thunk;
public class BaseRecyclerViewFastScrollBar {
public interface FastScrollFocusableView {
- void setFastScrollFocused(boolean focused, boolean animated);
+ void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
}
private final static int MAX_TRACK_ALPHA = 30;
@@ -189,7 +190,8 @@ public class BaseRecyclerViewFastScrollBar {
// Check if we should start scrolling, but ignore this fastscroll gesture if we have
// exceeded some fixed movement
mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop();
- if (!mIsDragging && !mIgnoreDragGesture && isNearThumb(downX, lastY) &&
+ if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
+ isNearThumb(downX, lastY) &&
Math.abs(y - downY) > config.getScaledTouchSlop()) {
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mIsDragging = true;
@@ -198,7 +200,7 @@ public class BaseRecyclerViewFastScrollBar {
}
mTouchOffset += (lastY - downY);
mPopup.animateVisibility(true);
- animateScrollbar(true);
+ showActiveScrollbar(true);
}
if (mIsDragging) {
// Update the fastscroller section name at this touch position
@@ -209,7 +211,7 @@ public class BaseRecyclerViewFastScrollBar {
(bottom - top));
mPopup.setSectionName(sectionName);
mPopup.animateVisibility(!sectionName.isEmpty());
- mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+ mRv.invalidate(mPopup.updateFastScrollerBounds(lastY));
mLastTouchY = boundedY;
}
break;
@@ -221,7 +223,7 @@ public class BaseRecyclerViewFastScrollBar {
if (mIsDragging) {
mIsDragging = false;
mPopup.animateVisibility(false);
- animateScrollbar(false);
+ showActiveScrollbar(false);
}
break;
}
@@ -245,7 +247,7 @@ public class BaseRecyclerViewFastScrollBar {
/**
* Animates the width and color of the scrollbar.
*/
- private void animateScrollbar(boolean isScrolling) {
+ private void showActiveScrollbar(boolean isScrolling) {
if (mScrollbarAnimator != null) {
mScrollbarAnimator.cancel();
}
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
index aeeb5156da..ebaba18805 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
@@ -77,26 +77,26 @@ public class BaseRecyclerViewFastScrollPopup {
* Updates the bounds for the fast scroller.
* @return the invalidation rect for this update.
*/
- public Rect updateFastScrollerBounds(BaseRecyclerView rv, int lastTouchY) {
+ public Rect updateFastScrollerBounds(int lastTouchY) {
mInvalidateRect.set(mBgBounds);
if (isVisible()) {
// Calculate the dimensions and position of the fast scroller popup
- int edgePadding = rv.getMaxScrollbarWidth();
+ 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 = rv.getBackgroundPadding().left + (2 * rv.getMaxScrollbarWidth());
+ mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth());
mBgBounds.right = mBgBounds.left + bgWidth;
} else {
- mBgBounds.right = rv.getWidth() - rv.getBackgroundPadding().right -
- (2 * rv.getMaxScrollbarWidth());
+ 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, rv.getHeight() - edgePadding - bgHeight));
+ Math.min(mBgBounds.top, mRv.getHeight() - edgePadding - bgHeight));
mBgBounds.bottom = mBgBounds.top + bgHeight;
} else {
mBgBounds.setEmpty();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5070878244..09ec60c50f 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -25,8 +24,9 @@ import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Region;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
@@ -37,13 +37,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
import com.android.launcher3.IconCache.IconLoadRequest;
import com.android.launcher3.model.PackageItemInfo;
+import java.text.NumberFormat;
+
/**
* TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
* because we want to make the bubble taller than the text and TextView's clip is
@@ -63,13 +63,6 @@ public class BubbleTextView extends TextView
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
- private static final float FAST_SCROLL_FOCUS_MAX_SCALE = 1.15f;
- private static final int FAST_SCROLL_FOCUS_MODE_NONE = 0;
- private static final int FAST_SCROLL_FOCUS_MODE_SCALE_ICON = 1;
- private static final int FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG = 2;
- private static final int FAST_SCROLL_FOCUS_FADE_IN_DURATION = 175;
- private static final int FAST_SCROLL_FOCUS_FADE_OUT_DURATION = 125;
-
private final Launcher mLauncher;
private Drawable mIcon;
private final Drawable mBackground;
@@ -93,12 +86,6 @@ public class BubbleTextView extends TextView
private boolean mIgnorePressedStateChange;
private boolean mDisableRelayout = false;
- private ObjectAnimator mFastScrollFocusAnimator;
- private Paint mFastScrollFocusBgPaint;
- private float mFastScrollFocusFraction;
- private boolean mFastScrollFocused;
- private final int mFastScrollMode = FAST_SCROLL_FOCUS_MODE_SCALE_ICON;
-
private IconLoadRequest mIconLoadRequest;
public BubbleTextView(Context context) {
@@ -126,7 +113,7 @@ public class BubbleTextView extends TextView
if (display == DISPLAY_WORKSPACE) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
} else if (display == DISPLAY_ALL_APPS) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.allAppsIconTextSizeSp);
defaultIconSize = grid.allAppsIconSizePx;
}
@@ -151,13 +138,6 @@ public class BubbleTextView extends TextView
setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
}
- if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG) {
- mFastScrollFocusBgPaint = new Paint();
- mFastScrollFocusBgPaint.setAntiAlias(true);
- mFastScrollFocusBgPaint.setColor(
- getResources().getColor(R.color.container_fastscroll_thumb_active_color));
- }
-
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
}
@@ -170,8 +150,9 @@ public class BubbleTextView extends TextView
Bitmap b = info.getIcon(iconCache);
FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
- iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
-
+ if (info.isDisabled()) {
+ iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
+ }
setIcon(iconDrawable, mIconSize);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
@@ -185,7 +166,11 @@ public class BubbleTextView extends TextView
}
public void applyFromApplicationInfo(AppInfo info) {
- setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize);
+ FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(info.iconBitmap);
+ if (info.isDisabled()) {
+ iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
+ }
+ setIcon(iconDrawable, mIconSize);
setText(info.title);
if (info.contentDescription != null) {
setContentDescription(info.contentDescription);
@@ -210,6 +195,15 @@ public class BubbleTextView extends TextView
verifyHighRes();
}
+ /**
+ * Used for measurement only, sets some dummy values on this view.
+ */
+ public void applyDummyInfo() {
+ ColorDrawable d = new ColorDrawable();
+ setIcon(mLauncher.resizeIconDrawable(d), mIconSize);
+ setText("");
+ }
+
/**
* Overrides the default long press timeout.
*/
@@ -259,7 +253,15 @@ public class BubbleTextView extends TextView
private void updateIconState() {
if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).setPressed(isPressed() || mStayPressed);
+ FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
+ if (getTag() instanceof ItemInfo
+ && ((ItemInfo) getTag()).isDisabled()) {
+ d.animateState(FastBitmapDrawable.State.DISABLED);
+ } else if (isPressed() || mStayPressed) {
+ d.animateState(FastBitmapDrawable.State.PRESSED);
+ } else {
+ d.animateState(FastBitmapDrawable.State.NORMAL);
+ }
}
}
@@ -362,18 +364,7 @@ public class BubbleTextView extends TextView
@Override
public void draw(Canvas canvas) {
if (!mCustomShadowsEnabled) {
- // Draw the fast scroll focus bg if we have one
- if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG &&
- mFastScrollFocusFraction > 0f) {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int iconCenterX = getScrollX() + (getWidth() / 2);
- int iconCenterY = getScrollY() + getPaddingTop() + (grid.iconSizePx / 2);
- canvas.drawCircle(iconCenterX, iconCenterY,
- mFastScrollFocusFraction * (getWidth() / 2), mFastScrollFocusBgPaint);
- }
-
super.draw(canvas);
-
return;
}
@@ -469,6 +460,11 @@ public class BubbleTextView extends TextView
((info.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE) ?
info.getInstallProgress() : 0)) : 100;
+ setContentDescription(progressLevel > 0 ?
+ getContext().getString(R.string.app_downloading_title, info.title,
+ NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
+ getContext().getString(R.string.app_waiting_download_title, info.title));
+
if (mIcon != null) {
final PreloadIconDrawable preloadDrawable;
if (mIcon instanceof PreloadIconDrawable) {
@@ -533,8 +529,13 @@ public class BubbleTextView extends TextView
*/
public void reapplyItemInfo(final ItemInfo info) {
if (getTag() == info) {
+ FastBitmapDrawable.State prevState = FastBitmapDrawable.State.NORMAL;
+ if (mIcon instanceof FastBitmapDrawable) {
+ prevState = ((FastBitmapDrawable) mIcon).getCurrentState();
+ }
mIconLoadRequest = null;
mDisableRelayout = true;
+
if (info instanceof AppInfo) {
applyFromApplicationInfo((AppInfo) info);
} else if (info instanceof ShortcutInfo) {
@@ -550,6 +551,13 @@ public class BubbleTextView extends TextView
} else if (info instanceof PackageItemInfo) {
applyFromPackageItemInfo((PackageItemInfo) info);
}
+
+ // If we are reapplying over an old icon, then we should update the new icon to the same
+ // state as the old icon
+ if (mIcon instanceof FastBitmapDrawable) {
+ ((FastBitmapDrawable) mIcon).setState(prevState);
+ }
+
mDisableRelayout = false;
}
}
@@ -583,55 +591,53 @@ public class BubbleTextView extends TextView
}
}
- // Setters & getters for the animation
- public void setFastScrollFocus(float fraction) {
- mFastScrollFocusFraction = fraction;
- if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_SCALE_ICON) {
- setScaleX(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f));
- setScaleY(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f));
- } else {
- invalidate();
- }
- }
-
- public float getFastScrollFocus() {
- return mFastScrollFocusFraction;
- }
-
@Override
- public void setFastScrollFocused(final boolean focused, boolean animated) {
- if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) {
+ public void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated) {
+ // We can only set the fast scroll focus state on a FastBitmapDrawable
+ if (!(mIcon instanceof FastBitmapDrawable)) {
return;
}
- if (mFastScrollFocused != focused) {
- mFastScrollFocused = focused;
-
- if (animated) {
- // Clean up the previous focus animator
- if (mFastScrollFocusAnimator != null) {
- mFastScrollFocusAnimator.cancel();
- }
- mFastScrollFocusAnimator = ObjectAnimator.ofFloat(this, "fastScrollFocus",
- focused ? 1f : 0f);
- if (focused) {
- mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator());
- } else {
- mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator());
- }
- mFastScrollFocusAnimator.setDuration(focused ?
- FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION);
- mFastScrollFocusAnimator.start();
- } else {
- mFastScrollFocusFraction = focused ? 1f : 0f;
+ FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
+ if (animated) {
+ FastBitmapDrawable.State prevState = d.getCurrentState();
+ if (d.animateState(focusState)) {
+ // If the state was updated, then update the view accordingly
+ animate().scaleX(focusState.viewScale)
+ .scaleY(focusState.viewScale)
+ .setStartDelay(getStartDelayForStateChange(prevState, focusState))
+ .setDuration(d.getDurationForStateChange(prevState, focusState))
+ .start();
+ }
+ } else {
+ if (d.setState(focusState)) {
+ // If the state was updated, then update the view accordingly
+ animate().cancel();
+ setScaleX(focusState.viewScale);
+ setScaleY(focusState.viewScale);
}
}
}
+ /**
+ * Returns the start delay when animating between certain {@link FastBitmapDrawable} states.
+ */
+ private static int getStartDelayForStateChange(final FastBitmapDrawable.State fromState,
+ final FastBitmapDrawable.State toState) {
+ switch (toState) {
+ case NORMAL:
+ switch (fromState) {
+ case FAST_SCROLL_HIGHLIGHTED:
+ return FastBitmapDrawable.FAST_SCROLL_INACTIVE_DURATION / 4;
+ }
+ }
+ return 0;
+ }
+
/**
* Interface to be implemented by the grand parent to allow click shadow effect.
*/
- public static interface BubbleTextShadowHandler {
+ public interface BubbleTextShadowHandler {
void setPressedIcon(BubbleTextView icon, Bitmap background);
}
}
diff --git a/src/com/android/launcher3/BuildInfo.java b/src/com/android/launcher3/BuildInfo.java
deleted file mode 100644
index b49ee0d9b8..0000000000
--- a/src/com/android/launcher3/BuildInfo.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.android.launcher3;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-public class BuildInfo {
- private static final boolean DBG = false;
- private static final String TAG = "BuildInfo";
-
- public boolean isDogfoodBuild() {
- return false;
- }
-
- public static BuildInfo loadByName(String className) {
- if (TextUtils.isEmpty(className)) return new BuildInfo();
-
- if (DBG) Log.d(TAG, "Loading BuildInfo: " + className);
- try {
- Class> cls = Class.forName(className);
- return (BuildInfo) cls.newInstance();
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Bad BuildInfo class", e);
- } catch (InstantiationException e) {
- Log.e(TAG, "Bad BuildInfo class", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Bad BuildInfo class", e);
- } catch (ClassCastException e) {
- Log.e(TAG, "Bad BuildInfo class", e);
- }
- return new BuildInfo();
- }
-}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 2baa6d8eed..40a4678dec 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -34,6 +34,7 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;
@@ -123,6 +124,10 @@ public abstract class ButtonDropTarget extends TextView
mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
setTextColor(mHoverColor);
}
+ if (d.stateAnnouncer != null) {
+ d.stateAnnouncer.cancel();
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
@Override
@@ -301,14 +306,10 @@ public abstract class ButtonDropTarget extends TextView
setOnClickListener(enable ? this : null);
}
- protected String getAccessibilityDropConfirmation() {
- return null;
- }
-
@Override
public void onClick(View v) {
LauncherAppState.getInstance().getAccessibilityDelegate()
- .handleAccessibleDrop(this, null, getAccessibilityDropConfirmation());
+ .handleAccessibleDrop(this, null, null);
}
public int getTextColor() {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 84e2d49c23..94e3e41419 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -25,7 +25,6 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -53,6 +52,7 @@ import com.android.launcher3.FolderIcon.FolderRingAnimator;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
+import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -66,7 +66,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
- static final String TAG = "CellLayout";
+ private static final String TAG = "CellLayout";
+ private static final boolean LOGD = false;
private Launcher mLauncher;
@Thunk int mCellWidth;
@@ -84,6 +85,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
private int mMaxGap;
private boolean mDropPending = false;
private boolean mIsDragTarget = true;
+ private boolean mJailContent = true;
// These are temporary variables to prevent having to allocate a new object just to
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@@ -187,7 +189,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mLauncher = (Launcher) context;
DeviceProfile grid = mLauncher.getDeviceProfile();
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
@@ -201,10 +202,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mPreviousReorderDirection[0] = INVALID_DIRECTION;
mPreviousReorderDirection[1] = INVALID_DIRECTION;
- a.recycle();
-
setAlwaysDrawnWithCacheEnabled(false);
-
final Resources res = getResources();
mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
@@ -244,9 +242,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// If an animation is started and then stopped very quickly, we can still
// get spurious updates we've cleared the tag. Guard against this.
if (outline == null) {
- @SuppressWarnings("all") // suppress dead code warning
- final boolean debug = false;
- if (debug) {
+ if (LOGD) {
Object val = animation.getAnimatedValue();
Log.d(TAG, "anim " + thisIndex + " update: " + val +
", isStopped " + anim.isStopped());
@@ -424,10 +420,36 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
}
}
- boolean getIsDragOverlapping() {
+ public boolean getIsDragOverlapping() {
return mIsDragOverlapping;
}
+ public void disableJailContent() {
+ mJailContent = false;
+ }
+
+ @Override
+ protected void dispatchSaveInstanceState(SparseArray container) {
+ if (mJailContent) {
+ ParcelableSparseArray jail = getJailedArray(container);
+ super.dispatchSaveInstanceState(jail);
+ container.put(R.id.cell_layout_jail_id, jail);
+ } else {
+ super.dispatchSaveInstanceState(container);
+ }
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray container) {
+ super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
+ }
+
+ private ParcelableSparseArray getJailedArray(SparseArray container) {
+ final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
+ return parcelable instanceof ParcelableSparseArray ?
+ (ParcelableSparseArray) parcelable : new ParcelableSparseArray();
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (!mIsDragTarget) {
@@ -630,6 +652,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
child.setId(childId);
+ if (LOGD) {
+ Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
+ }
mShortcutsAndWidgets.addView(child, index, lp);
if (markCells) markCellsAsOccupiedForView(child);
@@ -873,9 +898,12 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
- (mCountX * mCellWidth);
- int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
+ boolean isFullscreen = mShortcutsAndWidgets.getChildCount() > 0 &&
+ ((LayoutParams) mShortcutsAndWidgets.getChildAt(0).getLayoutParams()).isFullscreen;
+ int left = getPaddingLeft();
+ if (!isFullscreen) {
+ left += (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
+ }
int top = getPaddingTop();
mTouchFeedbackView.layout(left, top,
@@ -886,6 +914,15 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
top + b - t);
}
+ /**
+ * Returns the amount of space left over after subtracting padding and cells. This space will be
+ * very small, a few pixels at most, and is a result of rounding down when calculating the cell
+ * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
+ */
+ public int getUnusedHorizontalSpace() {
+ return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -1018,8 +1055,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
return false;
}
- void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
- int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
+ void visualizeDropLocation(View v, Bitmap dragOutline, int cellX, int cellY, int spanX,
+ int spanY, boolean resize, DropTarget.DragObject dragObject) {
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
@@ -1028,6 +1065,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
}
if (cellX != oldDragCellX || cellY != oldDragCellY) {
+ Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
+ Rect dragRegion = dragObject.dragView.getDragRegion();
+
mDragCell[0] = cellX;
mDragCell[1] = cellY;
// Find the top left corner of the rect the object will occupy
@@ -1079,6 +1119,18 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
mDragOutlineAnims[mDragOutlineCurrent].animateIn();
+
+ if (dragObject.stateAnnouncer != null) {
+ String msg;
+ if (isHotseat()) {
+ msg = getContext().getString(R.string.move_to_hotseat_position,
+ Math.max(cellX, cellY) + 1);
+ } else {
+ msg = getContext().getString(R.string.move_to_empty_cell,
+ cellY + 1, cellX + 1);
+ }
+ dragObject.stateAnnouncer.announce(msg);
+ }
}
}
@@ -2133,8 +2185,15 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
}
ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
a = va;
- va.setRepeatMode(ValueAnimator.REVERSE);
- va.setRepeatCount(ValueAnimator.INFINITE);
+
+ // Animations are disabled in power save mode, causing the repeated animation to jump
+ // spastically between beginning and end states. Since this looks bad, we don't repeat
+ // the animation in power save mode.
+ if (!Utilities.isPowerSaverOn(getContext())) {
+ va.setRepeatMode(ValueAnimator.REVERSE);
+ va.setRepeatCount(ValueAnimator.INFINITE);
+ }
+
va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
va.setStartDelay((int) (Math.random() * 60));
va.addUpdateListener(new AnimatorUpdateListener() {
@@ -2655,6 +2714,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.dropped = true;
child.requestLayout();
+ markCellsAsOccupiedForView(child);
}
}
diff --git a/src/com/android/launcher3/CommonAppTypeParser.java b/src/com/android/launcher3/CommonAppTypeParser.java
index 5314ecff17..001e044f9e 100644
--- a/src/com/android/launcher3/CommonAppTypeParser.java
+++ b/src/com/android/launcher3/CommonAppTypeParser.java
@@ -25,7 +25,7 @@ import android.util.Log;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.nano.BackupProtos.Favorite;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParserException;
@@ -93,7 +93,7 @@ public class CommonAppTypeParser implements LayoutParserCallback {
public MyLayoutParser() {
super(CommonAppTypeParser.this.mContext, null, CommonAppTypeParser.this,
- CommonAppTypeParser.this.mContext.getResources(), mResId, TAG_RESOLVE, 0);
+ CommonAppTypeParser.this.mContext.getResources(), mResId, TAG_RESOLVE);
}
@Override
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index 7b91c675be..2bba380062 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -48,9 +48,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout {
}
public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
- LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag,
- int hotseatAllAppsRank) {
- super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag, hotseatAllAppsRank);
+ LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag) {
+ super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag);
}
@Override
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9c8659c293..edaf525d65 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -19,7 +19,6 @@ package com.android.launcher3;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.PointF;
-import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;
@@ -67,44 +66,14 @@ public class DeleteDropTarget extends ButtonDropTarget {
/**
* Removes the item from the workspace. If the view is not null, it also removes the view.
- * @return true if the item was removed.
*/
- public static boolean removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
- if (item instanceof ShortcutInfo) {
- LauncherModel.deleteItemFromDatabase(launcher, item);
- } else if (item instanceof FolderInfo) {
- FolderInfo folder = (FolderInfo) item;
- launcher.removeFolder(folder);
- LauncherModel.deleteFolderContentsFromDatabase(launcher, folder);
- } else if (item instanceof LauncherAppWidgetInfo) {
- final LauncherAppWidgetInfo widget = (LauncherAppWidgetInfo) item;
-
- // Remove the widget from the workspace
- launcher.removeAppWidget(widget);
- LauncherModel.deleteItemFromDatabase(launcher, widget);
-
- final LauncherAppWidgetHost appWidgetHost = launcher.getAppWidgetHost();
-
- if (appWidgetHost != null && !widget.isCustomWidget()
- && widget.isWidgetIdValid()) {
- // Deleting an app widget ID is a void call but writes to disk before returning
- // to the caller...
- new AsyncTask() {
- public Void doInBackground(Void ... args) {
- appWidgetHost.deleteAppWidgetId(widget.appWidgetId);
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- } else {
- return false;
- }
-
- if (view != null) {
- launcher.getWorkspace().removeWorkspaceItem(view);
- launcher.getWorkspace().stripEmptyScreens();
- }
- return true;
+ public static void removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
+ // Remove the item from launcher and the db, we can ignore the containerInfo in this call
+ // because we already remove the drag view from the folder (if the drag originated from
+ // a folder) in Folder.beginDrag()
+ launcher.removeItem(view, item, true /* deleteFromDb */);
+ launcher.getWorkspace().stripEmptyScreens();
+ launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
}
@Override
@@ -156,9 +125,4 @@ public class DeleteDropTarget extends ButtonDropTarget {
dragLayer.animateView(d.dragView, fling, duration, tInterpolator, onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
}
-
- @Override
- protected String getAccessibilityDropConfirmation() {
- return getResources().getString(R.string.item_removed);
- }
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 774594fe2c..380c6b148b 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -49,6 +49,12 @@ public class DeviceProfile {
public final int heightPx;
public final int availableWidthPx;
public final int availableHeightPx;
+ /**
+ * The maximum amount of left/right workspace padding as a percentage of the screen width.
+ * To be clear, this means that up to 7% of the screen width can be used as left padding, and
+ * 7% of the screen width can be used as right padding.
+ */
+ private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
// Overview mode
private final int overviewModeMinIconZoneHeightPx;
@@ -84,18 +90,25 @@ public class DeviceProfile {
public int hotseatCellWidthPx;
public int hotseatCellHeightPx;
public int hotseatIconSizePx;
- private int hotseatBarHeightPx;
+ private int normalHotseatBarHeightPx, shortHotseatBarHeightPx;
+ private int hotseatBarHeightPx; // One of the above.
// All apps
public int allAppsNumCols;
public int allAppsNumPredictiveCols;
public int allAppsButtonVisualSize;
public final int allAppsIconSizePx;
- public final int allAppsIconTextSizePx;
+ public final float allAppsIconTextSizeSp;
// QSB
- private int searchBarSpaceWidthPx;
- private int searchBarSpaceHeightPx;
+ private int searchBarWidgetInternalPaddingTop, searchBarWidgetInternalPaddingBottom;
+ private int searchBarTopPaddingPx;
+ private int tallSearchBarNegativeTopPaddingPx, normalSearchBarTopExtraPaddingPx;
+ private int searchBarTopExtraPaddingPx; // One of the above.
+ private int normalSearchBarBottomPaddingPx, tallSearchBarBottomPaddingPx;
+ private int searchBarBottomPaddingPx; // One of the above.
+ private int normalSearchBarSpaceHeightPx, tallSearchBarSpaceHeightPx;
+ private int searchBarSpaceHeightPx; // One of the above.
public DeviceProfile(Context context, InvariantDeviceProfile inv,
Point minSize, Point maxSize,
@@ -139,7 +152,7 @@ public class DeviceProfile {
res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
// AllApps uses the original non-scaled icon text size
- allAppsIconTextSizePx = Utilities.pxFromDp(inv.iconTextSize, dm);
+ allAppsIconTextSizeSp = inv.iconTextSize;
// AllApps uses the original non-scaled icon size
allAppsIconSizePx = Utilities.pxFromDp(inv.iconSize, dm);
@@ -167,7 +180,8 @@ public class DeviceProfile {
private void computeAllAppsButtonSize(Context context) {
Resources res = context.getResources();
float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
- allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
+ allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)) - context.getResources()
+ .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
}
private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
@@ -196,10 +210,31 @@ public class DeviceProfile {
hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
// Search Bar
- searchBarSpaceWidthPx = Math.min(widthPx,
- res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
- searchBarSpaceHeightPx = getSearchBarTopOffset()
- + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+ normalSearchBarSpaceHeightPx = res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_search_bar_height);
+ tallSearchBarSpaceHeightPx = res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_search_bar_height_tall);
+ searchBarWidgetInternalPaddingTop = res.getDimensionPixelSize(
+ R.dimen.qsb_internal_padding_top);
+ searchBarWidgetInternalPaddingBottom = res.getDimensionPixelSize(
+ R.dimen.qsb_internal_padding_bottom);
+ normalSearchBarTopExtraPaddingPx = res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_search_bar_extra_top_padding);
+ tallSearchBarNegativeTopPaddingPx = res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_search_bar_negative_top_padding_short);
+ if (isTablet && !isVerticalBarLayout()) {
+ searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
+ normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding_tablet);
+ tallSearchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
+ } else {
+ searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
+ normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding);
+ tallSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom
+ + res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_search_bar_bottom_negative_padding_short);
+ }
// Calculate the actual text height
Paint textPaint = new Paint();
@@ -211,12 +246,16 @@ public class DeviceProfile {
dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
// Hotseat
- hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+ normalHotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+ shortHotseatBarHeightPx = iconSizePx + 2 * edgeMarginPx;
hotseatCellWidthPx = iconSizePx;
hotseatCellHeightPx = iconSizePx;
// Folder
- folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
+ int folderCellPadding = isTablet || isLandscape ? 6 * edgeMarginPx : 3 * edgeMarginPx;
+ // Don't let the folder get too close to the edges of the screen.
+ folderCellWidthPx = Math.min(cellWidthPx + folderCellPadding,
+ (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
folderCellHeightPx = cellHeightPx + edgeMarginPx;
folderBackgroundOffset = -edgeMarginPx;
folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
@@ -231,34 +270,42 @@ public class DeviceProfile {
int allAppsCellWidthGap =
res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap);
int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx;
- int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
+ int numAppsCols = (availableAppsWidthPx + allAppsCellWidthGap - appsViewLeftMarginPx) /
(allAppsIconSizePx + allAppsCellWidthGap);
int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols);
allAppsNumCols = numAppsCols;
allAppsNumPredictiveCols = numPredictiveAppCols;
}
- /** Returns the search bar top offset */
- private int getSearchBarTopOffset() {
- if (isTablet && !isVerticalBarLayout()) {
- return 4 * edgeMarginPx;
- } else {
- return 2 * edgeMarginPx;
+ /** Returns the amount of extra space to allocate to the search bar for vertical padding. */
+ private int getSearchBarTotalVerticalPadding() {
+ return searchBarTopPaddingPx + searchBarTopExtraPaddingPx + searchBarBottomPaddingPx;
+ }
+
+ /** Returns the width and height of the search bar, ignoring any padding. */
+ public Point getSearchBarDimensForWidgetOpts(Resources res) {
+ Rect searchBarBounds = getSearchBarBounds(Utilities.isRtl(res));
+ if (isVerticalBarLayout()) {
+ return new Point(searchBarBounds.width(), searchBarBounds.height());
}
+ int widgetInternalPadding = searchBarWidgetInternalPaddingTop +
+ searchBarWidgetInternalPaddingBottom;
+ return new Point(searchBarBounds.width(), searchBarSpaceHeightPx + widgetInternalPadding);
}
/** Returns the search bar bounds in the current orientation */
public Rect getSearchBarBounds(boolean isLayoutRtl) {
Rect bounds = new Rect();
- if (isLandscape && transposeLayoutWithOrientation) {
+ if (isVerticalBarLayout()) {
if (isLayoutRtl) {
- bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
+ bounds.set(availableWidthPx - normalSearchBarSpaceHeightPx, edgeMarginPx,
availableWidthPx, availableHeightPx - edgeMarginPx);
} else {
- bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
+ bounds.set(0, edgeMarginPx, normalSearchBarSpaceHeightPx,
availableHeightPx - edgeMarginPx);
}
} else {
+ int boundsBottom = searchBarSpaceHeightPx + getSearchBarTotalVerticalPadding();
if (isTablet) {
// Pad the left and right of the workspace to ensure consistent spacing
// between all icons
@@ -267,14 +314,13 @@ public class DeviceProfile {
// that into account here too.
int gap = (int) ((width - 2 * edgeMarginPx -
(inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)));
- bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
- availableWidthPx - (edgeMarginPx + gap),
- searchBarSpaceHeightPx);
+ bounds.set(edgeMarginPx + gap, 0,
+ availableWidthPx - (edgeMarginPx + gap), boundsBottom);
} else {
bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
- getSearchBarTopOffset(),
+ 0,
availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
- defaultWidgetPadding.right), searchBarSpaceHeightPx);
+ defaultWidgetPadding.right), boundsBottom);
}
}
return bounds;
@@ -284,43 +330,46 @@ public class DeviceProfile {
Rect getWorkspacePadding(boolean isLayoutRtl) {
Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
Rect padding = new Rect();
- if (isLandscape && transposeLayoutWithOrientation) {
+ if (isVerticalBarLayout()) {
// Pad the left and right of the workspace with search/hotseat bar sizes
if (isLayoutRtl) {
- padding.set(hotseatBarHeightPx, edgeMarginPx,
+ padding.set(normalHotseatBarHeightPx, edgeMarginPx,
searchBarBounds.width(), edgeMarginPx);
} else {
padding.set(searchBarBounds.width(), edgeMarginPx,
- hotseatBarHeightPx, edgeMarginPx);
+ normalHotseatBarHeightPx, edgeMarginPx);
}
} else {
+ int paddingTop = searchBarBounds.bottom;
+ int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
if (isTablet) {
// Pad the left and right of the workspace to ensure consistent spacing
// between all icons
float gapScale = 1f + (dragViewScale - 1f) / 2f;
int width = getCurrentWidth();
int height = getCurrentHeight();
- int paddingTop = searchBarBounds.bottom;
- int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
- int availableWidth = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
- (inv.numColumns * gapScale * cellWidthPx)));
- int availableHeight = Math.max(0, height - paddingTop - paddingBottom
+ // The amount of screen space available for left/right padding.
+ int availablePaddingX = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
+ ((inv.numColumns - 1) * gapScale * cellWidthPx)));
+ availablePaddingX = (int) Math.min(availablePaddingX,
+ width * MAX_HORIZONTAL_PADDING_PERCENT);
+ int availablePaddingY = Math.max(0, height - paddingTop - paddingBottom
- (int) (2 * inv.numRows * cellHeightPx));
- padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
- availableWidth / 2, paddingBottom + availableHeight / 2);
+ padding.set(availablePaddingX / 2, paddingTop + availablePaddingY / 2,
+ availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
} else {
// Pad the top and bottom of the workspace with search/hotseat bar sizes
padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
- searchBarBounds.bottom,
+ paddingTop,
desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
- hotseatBarHeightPx + pageIndicatorHeightPx);
+ paddingBottom);
}
}
return padding;
}
private int getWorkspacePageSpacing(boolean isLayoutRtl) {
- if ((isLandscape && transposeLayoutWithOrientation) || isLargeTablet) {
+ if (isVerticalBarLayout() || isLargeTablet) {
// In landscape mode the page spacing is set to the default.
return defaultPageSpacingPx;
} else {
@@ -341,7 +390,7 @@ public class DeviceProfile {
// The rect returned will be extended to below the system ui that covers the workspace
Rect getHotseatRect() {
if (isVerticalBarLayout()) {
- return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+ return new Rect(availableWidthPx - normalHotseatBarHeightPx, 0,
Integer.MAX_VALUE, availableHeightPx);
} else {
return new Rect(0, availableHeightPx - hotseatBarHeightPx,
@@ -357,8 +406,9 @@ public class DeviceProfile {
}
/**
- * When {@code true}, hotseat is on the bottom row when in landscape mode.
- * If {@code false}, hotseat is on the right column when in landscape mode.
+ * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
+ * When {@code false}, either device is in portrait mode or the device is in landscape mode and
+ * the hotseat is on the bottom row.
*/
boolean isVerticalBarLayout() {
return isLandscape && transposeLayoutWithOrientation;
@@ -378,19 +428,38 @@ public class DeviceProfile {
return visibleChildren;
}
+ // TODO(twickham): b/25154513
+ public void setSearchBarHeight(int searchBarHeight) {
+ if (searchBarHeight == LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL) {
+ hotseatBarHeightPx = shortHotseatBarHeightPx;
+ searchBarSpaceHeightPx = tallSearchBarSpaceHeightPx;
+ searchBarBottomPaddingPx = tallSearchBarBottomPaddingPx;
+ searchBarTopExtraPaddingPx = isPhone ? tallSearchBarNegativeTopPaddingPx
+ : normalSearchBarTopExtraPaddingPx;
+ } else {
+ hotseatBarHeightPx = normalHotseatBarHeightPx;
+ searchBarSpaceHeightPx = normalSearchBarSpaceHeightPx;
+ searchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
+ searchBarTopExtraPaddingPx = normalSearchBarTopExtraPaddingPx;
+ }
+ }
+
public void layout(Launcher launcher) {
FrameLayout.LayoutParams lp;
boolean hasVerticalBarLayout = isVerticalBarLayout();
final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
// Layout the search bar space
+ Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
View searchBar = launcher.getSearchDropTargetBar();
lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+ lp.width = searchBarBounds.width();
+ lp.height = searchBarBounds.height();
+ lp.topMargin = searchBarTopExtraPaddingPx;
if (hasVerticalBarLayout) {
// Vertical search bar space -- The search bar is fixed in the layout to be on the left
// of the screen regardless of RTL
lp.gravity = Gravity.LEFT;
- lp.width = searchBarSpaceHeightPx;
LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
targets.setOrientation(LinearLayout.VERTICAL);
@@ -400,11 +469,7 @@ public class DeviceProfile {
} else {
// Horizontal search bar space
- lp.gravity = Gravity.TOP;
- lp.height = searchBarSpaceHeightPx;
-
- LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
- targets.getLayoutParams().width = searchBarSpaceWidthPx;
+ lp.gravity = Gravity.TOP|Gravity.CENTER_HORIZONTAL;
}
searchBar.setLayoutParams(lp);
@@ -420,11 +485,18 @@ public class DeviceProfile {
// Layout the hotseat
View hotseat = launcher.findViewById(R.id.hotseat);
lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
+ // We want the edges of the hotseat to line up with the edges of the workspace, but the
+ // icons in the hotseat are a different size, and so don't line up perfectly. To account for
+ // this, we pad the left and right of the hotseat with half of the difference of a workspace
+ // cell vs a hotseat cell.
+ float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
+ float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
+ int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
if (hasVerticalBarLayout) {
// Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
// screen regardless of RTL
lp.gravity = Gravity.RIGHT;
- lp.width = hotseatBarHeightPx;
+ lp.width = normalHotseatBarHeightPx;
lp.height = LayoutParams.MATCH_PARENT;
hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
} else if (isTablet) {
@@ -432,17 +504,18 @@ public class DeviceProfile {
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = hotseatBarHeightPx;
- hotseat.setPadding(edgeMarginPx + padding.left, 0,
- edgeMarginPx + padding.right,
- 2 * edgeMarginPx);
+ hotseat.findViewById(R.id.layout).setPadding(
+ hotseatAdjustment + padding.left, 0,
+ hotseatAdjustment + padding.right, 2 * edgeMarginPx);
} else {
// For phones, layout the hotseat without any bottom margin
// to ensure that we have space for the folders
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = hotseatBarHeightPx;
- hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
- 2 * edgeMarginPx, 0);
+ hotseat.findViewById(R.id.layout).setPadding(
+ hotseatAdjustment + padding.left, 0,
+ hotseatAdjustment + padding.right, 0);
}
hotseat.setLayoutParams(lp);
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 2191455d53..2f0bfe5c77 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -32,8 +32,10 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -145,8 +147,6 @@ public class DragController {
/**
* Used to create a new DragLayer from XML.
- *
- * @param context The application's context.
*/
public DragController(Launcher launcher) {
Resources r = launcher.getResources();
@@ -239,6 +239,9 @@ public class DragController {
mDragObject = new DropTarget.DragObject();
+ final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
+ registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
+
mDragObject.dragComplete = false;
if (mIsAccessibleDrag) {
// For an accessible drag, we assume the view is being dragged from the center.
@@ -248,14 +251,12 @@ public class DragController {
} else {
mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+ mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
}
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
- final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
- registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
-
if (dragOffset != null) {
dragView.setDragVisualizeOffset(new Point(dragOffset));
}
@@ -340,15 +341,15 @@ public class DragController {
}
endDrag();
}
- public void onAppsRemoved(final ArrayList packageNames, HashSet cns) {
+
+ public void onAppsRemoved(final HashSet packageNames, HashSet cns) {
// Cancel the current drag if we are removing an app that we are dragging
if (mDragObject != null) {
Object rawDragInfo = mDragObject.dragInfo;
if (rawDragInfo instanceof ShortcutInfo) {
ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
for (ComponentName componentName : cns) {
- // Added null checks to prevent NPE we've seen in the wild
- if (dragInfo != null && dragInfo.intent != null) {
+ if (dragInfo.intent != null) {
ComponentName cn = dragInfo.intent.getComponent();
boolean isSameComponent = cn != null && (cn.equals(componentName) ||
packageNames.contains(cn.getPackageName()));
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 1c18747c10..ad9063c25a 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -565,10 +565,6 @@ public class DragLayer extends InsettableFrameLayout {
resizeFrame.snapToWidget(false);
}
- public void animateViewIntoPosition(DragView dragView, final View child) {
- animateViewIntoPosition(dragView, child, null, null);
- }
-
public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
int duration) {
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index 2acfc61403..a584667cac 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -321,10 +321,10 @@ public class DragView extends View {
setTranslationY(touchY - mRegistrationY);
// Post the animation to skip other expensive work happening on the first frame
post(new Runnable() {
- public void run() {
- mAnim.start();
- }
- });
+ public void run() {
+ mAnim.start();
+ }
+ });
}
public void cancelAnimation() {
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index c8fac54669..434059168c 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -19,6 +19,8 @@ package com.android.launcher3;
import android.graphics.PointF;
import android.graphics.Rect;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+
/**
* Interface defining an object that can receive a drag.
*
@@ -64,6 +66,8 @@ public interface DropTarget {
/** Defers removing the DragView from the DragLayer until after the drop animation. */
public boolean deferDragViewCleanupPostAnimation = true;
+ public DragViewStateAnnouncer stateAnnouncer;
+
public DragObject() {
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 28e923e67a..d7f1d8677d 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Bitmap;
@@ -28,13 +29,40 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.SparseArray;
+import android.view.animation.DecelerateInterpolator;
public class FastBitmapDrawable extends Drawable {
- static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
+ /**
+ * The possible states that a FastBitmapDrawable can be in.
+ */
+ public enum State {
+
+ NORMAL (0f, 0f, 1f, new DecelerateInterpolator()),
+ PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
+ FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.15f, new DecelerateInterpolator()),
+ FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator()),
+ DISABLED (1f, 0.5f, 1f, new DecelerateInterpolator());
+
+ public final float desaturation;
+ public final float brightness;
+ /**
+ * Used specifically by the view drawing this FastBitmapDrawable.
+ */
+ public final float viewScale;
+ public final TimeInterpolator interpolator;
+
+ State(float desaturation, float brightness, float viewScale, TimeInterpolator interpolator) {
+ this.desaturation = desaturation;
+ this.brightness = brightness;
+ this.viewScale = viewScale;
+ this.interpolator = interpolator;
+ }
+ }
+
+ public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
@Override
public float getInterpolation(float input) {
@@ -47,42 +75,46 @@ public class FastBitmapDrawable extends Drawable {
}
}
};
- static final long CLICK_FEEDBACK_DURATION = 2000;
+ public static final int CLICK_FEEDBACK_DURATION = 2000;
+ public static final int FAST_SCROLL_HIGHLIGHT_DURATION = 225;
+ public static final int FAST_SCROLL_UNHIGHLIGHT_DURATION = 150;
+ public static final int FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION = 225;
+ public static final int FAST_SCROLL_INACTIVE_DURATION = 275;
- private static final int PRESSED_BRIGHTNESS = 100;
- private static ColorMatrix sGhostModeMatrix;
- private static final ColorMatrix sTempMatrix = new ColorMatrix();
+ // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
+ // reduce the value space to a smaller value V, which reduces the number of cached
+ // ColorMatrixColorFilters that we need to keep to V^2
+ private static final int REDUCED_FILTER_VALUE_SPACE = 48;
- /**
- * Store the brightness colors filters to optimize animations during icon press. This
- * only works for non-ghost-mode icons.
- */
- private static final SparseArray sCachedBrightnessFilter =
- new SparseArray();
+ // A cache of ColorFilters for optimizing brightness and saturation animations
+ private static final SparseArray sCachedFilter = new SparseArray<>();
- private static final int GHOST_MODE_MIN_COLOR_RANGE = 130;
+ // Temporary matrices used for calculation
+ private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
+ private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
private final Bitmap mBitmap;
- private int mAlpha;
+ private State mState = State.NORMAL;
+ // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
+ // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
+ private int mDesaturation = 0;
private int mBrightness = 0;
- private boolean mGhostModeEnabled = false;
+ private int mAlpha = 255;
+ private int mPrevUpdateKey = Integer.MAX_VALUE;
- private boolean mPressed = false;
- private ObjectAnimator mPressedAnimator;
+ // Animators for the fast bitmap drawable's properties
+ private AnimatorSet mPropertyAnimator;
public FastBitmapDrawable(Bitmap b) {
- mAlpha = 255;
mBitmap = b;
setBounds(0, 0, b.getWidth(), b.getHeight());
}
@Override
public void draw(Canvas canvas) {
- final Rect r = getBounds();
- // Draw the bitmap into the bounding rect
- canvas.drawBitmap(mBitmap, null, r, mPaint);
+ canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
}
@Override
@@ -136,96 +168,191 @@ public class FastBitmapDrawable extends Drawable {
}
/**
- * When enabled, the icon is grayed out and the contrast is increased to give it a 'ghost'
- * appearance.
+ * Animates this drawable to a new state.
+ *
+ * @return whether the state has changed.
*/
- public void setGhostModeEnabled(boolean enabled) {
- if (mGhostModeEnabled != enabled) {
- mGhostModeEnabled = enabled;
+ public boolean animateState(State newState) {
+ State prevState = mState;
+ if (mState != newState) {
+ mState = newState;
+
+ mPropertyAnimator = cancelAnimator(mPropertyAnimator);
+ mPropertyAnimator = new AnimatorSet();
+ mPropertyAnimator.playTogether(
+ ObjectAnimator
+ .ofFloat(this, "desaturation", newState.desaturation),
+ ObjectAnimator
+ .ofFloat(this, "brightness", newState.brightness));
+ mPropertyAnimator.setInterpolator(newState.interpolator);
+ mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
+ mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
+ mPropertyAnimator.start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Immediately sets this drawable to a new state.
+ *
+ * @return whether the state has changed.
+ */
+ public boolean setState(State newState) {
+ if (mState != newState) {
+ mState = newState;
+
+ mPropertyAnimator = cancelAnimator(mPropertyAnimator);
+
+ setDesaturation(newState.desaturation);
+ setBrightness(newState.brightness);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current state.
+ */
+ public State getCurrentState() {
+ return mState;
+ }
+
+ /**
+ * Returns the duration for the state change animation.
+ */
+ public static int getDurationForStateChange(State fromState, State toState) {
+ switch (toState) {
+ case NORMAL:
+ switch (fromState) {
+ case PRESSED:
+ return 0;
+ case FAST_SCROLL_HIGHLIGHTED:
+ case FAST_SCROLL_UNHIGHLIGHTED:
+ return FAST_SCROLL_INACTIVE_DURATION;
+ }
+ case PRESSED:
+ return CLICK_FEEDBACK_DURATION;
+ case FAST_SCROLL_HIGHLIGHTED:
+ return FAST_SCROLL_HIGHLIGHT_DURATION;
+ case FAST_SCROLL_UNHIGHLIGHTED:
+ switch (fromState) {
+ case NORMAL:
+ // When animating from normal state, take a little longer
+ return FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION;
+ default:
+ return FAST_SCROLL_UNHIGHLIGHT_DURATION;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the start delay when animating between certain fast scroll states.
+ */
+ public static int getStartDelayForStateChange(State fromState, State toState) {
+ switch (toState) {
+ case FAST_SCROLL_UNHIGHLIGHTED:
+ switch (fromState) {
+ case NORMAL:
+ return FAST_SCROLL_UNHIGHLIGHT_DURATION / 4;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
+ */
+ public void setDesaturation(float desaturation) {
+ int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
+ if (mDesaturation != newDesaturation) {
+ mDesaturation = newDesaturation;
updateFilter();
}
}
- public void setPressed(boolean pressed) {
- if (mPressed != pressed) {
- mPressed = pressed;
- if (mPressed) {
- mPressedAnimator = ObjectAnimator
- .ofInt(this, "brightness", PRESSED_BRIGHTNESS)
- .setDuration(CLICK_FEEDBACK_DURATION);
- mPressedAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
- mPressedAnimator.start();
- } else if (mPressedAnimator != null) {
- mPressedAnimator.cancel();
- setBrightness(0);
- }
- }
- invalidateSelf();
+ public float getDesaturation() {
+ return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
}
- public boolean isGhostModeEnabled() {
- return mGhostModeEnabled;
- }
-
- public int getBrightness() {
- return mBrightness;
- }
-
- public void setBrightness(int brightness) {
- if (mBrightness != brightness) {
- mBrightness = brightness;
+ /**
+ * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
+ */
+ public void setBrightness(float brightness) {
+ int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
+ if (mBrightness != newBrightness) {
+ mBrightness = newBrightness;
updateFilter();
- invalidateSelf();
}
}
+ public float getBrightness() {
+ return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
+ }
+
+ /**
+ * Updates the paint to reflect the current brightness and saturation.
+ */
private void updateFilter() {
- if (mGhostModeEnabled) {
- if (sGhostModeMatrix == null) {
- sGhostModeMatrix = new ColorMatrix();
- sGhostModeMatrix.setSaturation(0);
+ boolean usePorterDuffFilter = false;
+ int key = -1;
+ if (mDesaturation > 0) {
+ key = (mDesaturation << 16) | mBrightness;
+ } else if (mBrightness > 0) {
+ // Compose a key with a fully saturated icon if we are just animating brightness
+ key = (1 << 16) | mBrightness;
- // For ghost mode, set the color range to [GHOST_MODE_MIN_COLOR_RANGE, 255]
- float range = (255 - GHOST_MODE_MIN_COLOR_RANGE) / 255.0f;
- sTempMatrix.set(new float[] {
- range, 0, 0, 0, GHOST_MODE_MIN_COLOR_RANGE,
- 0, range, 0, 0, GHOST_MODE_MIN_COLOR_RANGE,
- 0, 0, range, 0, GHOST_MODE_MIN_COLOR_RANGE,
- 0, 0, 0, 1, 0 });
- sGhostModeMatrix.preConcat(sTempMatrix);
- }
+ // We found that in L, ColorFilters cause drawing artifacts with shadows baked into
+ // icons, so just use a PorterDuff filter when we aren't animating saturation
+ usePorterDuffFilter = true;
+ }
- if (mBrightness == 0) {
- mPaint.setColorFilter(new ColorMatrixColorFilter(sGhostModeMatrix));
- } else {
- setBrightnessMatrix(sTempMatrix, mBrightness);
- sTempMatrix.postConcat(sGhostModeMatrix);
- mPaint.setColorFilter(new ColorMatrixColorFilter(sTempMatrix));
- }
- } else if (mBrightness != 0) {
- ColorFilter filter = sCachedBrightnessFilter.get(mBrightness);
+ // Debounce multiple updates on the same frame
+ if (key == mPrevUpdateKey) {
+ return;
+ }
+ mPrevUpdateKey = key;
+
+ if (key != -1) {
+ ColorFilter filter = sCachedFilter.get(key);
if (filter == null) {
- filter = new PorterDuffColorFilter(Color.argb(mBrightness, 255, 255, 255),
- PorterDuff.Mode.SRC_ATOP);
- sCachedBrightnessFilter.put(mBrightness, filter);
+ float brightnessF = getBrightness();
+ int brightnessI = (int) (255 * brightnessF);
+ if (usePorterDuffFilter) {
+ filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
+ PorterDuff.Mode.SRC_ATOP);
+ } else {
+ float saturationF = 1f - getDesaturation();
+ sTempFilterMatrix.setSaturation(saturationF);
+ if (mBrightness > 0) {
+ // Brightness: C-new = C-old*(1-amount) + amount
+ float scale = 1f - brightnessF;
+ float[] mat = sTempBrightnessMatrix.getArray();
+ mat[0] = scale;
+ mat[6] = scale;
+ mat[12] = scale;
+ mat[4] = brightnessI;
+ mat[9] = brightnessI;
+ mat[14] = brightnessI;
+ sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
+ }
+ filter = new ColorMatrixColorFilter(sTempFilterMatrix);
+ }
+ sCachedFilter.append(key, filter);
}
mPaint.setColorFilter(filter);
} else {
mPaint.setColorFilter(null);
}
+ invalidateSelf();
}
- private static void setBrightnessMatrix(ColorMatrix matrix, int brightness) {
- // Brightness: C-new = C-old*(1-amount) + amount
- float scale = 1 - brightness / 255.0f;
- matrix.setScale(scale, scale, scale, 1);
- float[] array = matrix.getArray();
-
- // Add the amount to RGB components of the matrix, as per the above formula.
- // Fifth elements in the array correspond to the constant being added to
- // red, blue, green, and alpha channel respectively.
- array[4] = brightness;
- array[9] = brightness;
- array[14] = brightness;
+ private AnimatorSet cancelAnimator(AnimatorSet animator) {
+ if (animator != null) {
+ animator.removeAllListeners();
+ animator.cancel();
+ }
+ return null;
}
}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 57aec32801..d59c64431e 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -45,6 +45,23 @@ class HotseatIconKeyEventListener implements View.OnKeyListener {
}
}
+/**
+ * A keyboard listener we set on full screen pages (e.g. custom content).
+ */
+class FullscreenKeyEventListener implements View.OnKeyListener {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+ || keyCode == KeyEvent.KEYCODE_PAGE_DOWN || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
+ // Handle the key event just like a workspace icon would in these cases. In this case,
+ // it will basically act as if there is a single icon in the top left (so you could
+ // think of the fullscreen page as a focusable fullscreen widget).
+ return FocusHelper.handleIconKeyEvent(v, keyCode, event);
+ }
+ return false;
+ }
+}
+
public class FocusHelper {
private static final String TAG = "FocusHelper";
@@ -72,7 +89,6 @@ public class FocusHelper {
KeyEvent.keyCodeToString(keyCode)));
}
-
if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
if (LauncherAppState.isDogfoodBuild()) {
throw new IllegalStateException("Parent of the focused item is not supported.");
@@ -84,8 +100,6 @@ public class FocusHelper {
// Initialize variables.
final ShortcutAndWidgetContainer itemContainer = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout cellLayout = (CellLayout) itemContainer.getParent();
- final int countX = cellLayout.getCountX();
- final int countY = cellLayout.getCountY();
final int iconIndex = itemContainer.indexOfChild(v);
final FolderPagedView pagedView = (FolderPagedView) cellLayout.getParent();
@@ -96,8 +110,8 @@ public class FocusHelper {
int[][] matrix = FocusLogic.createSparseMatrix(cellLayout);
// Process focus.
- int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
- countY, matrix, iconIndex, pageIndex, pageCount, isLayoutRtl);
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
+ pageCount, isLayoutRtl);
if (newIconIndex == FocusLogic.NOOP) {
handleNoopKey(keyCode, v);
return consume;
@@ -114,7 +128,8 @@ public class FocusHelper {
pagedView.snapToPage(pageIndex - 1);
child = newParent.getChildAt(
((newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN)
- ^ newParent.invertLayoutHorizontally()) ? 0 : countX - 1, row);
+ ^ newParent.invertLayoutHorizontally()) ? 0 : matrix.length - 1,
+ row);
}
break;
case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
@@ -128,7 +143,7 @@ public class FocusHelper {
newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex - 1);
if (newParent != null) {
pagedView.snapToPage(pageIndex - 1);
- child = newParent.getChildAt(countX - 1, countY - 1);
+ child = newParent.getChildAt(matrix.length - 1, matrix[0].length - 1);
}
break;
case FocusLogic.NEXT_PAGE_FIRST_ITEM:
@@ -143,7 +158,8 @@ public class FocusHelper {
newParent = getCellLayoutChildrenForIndex(pagedView, pageIndex + 1);
if (newParent != null) {
pagedView.snapToPage(pageIndex + 1);
- child = FocusLogic.getAdjacentChildInNextPage(newParent, v, newIconIndex);
+ child = FocusLogic.getAdjacentChildInNextFolderPage(
+ newParent, v, newIconIndex);
}
break;
case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
@@ -174,7 +190,7 @@ public class FocusHelper {
}
/**
- * Handles key events in the workspace hot seat (bottom of the screen).
+ * Handles key events in the workspace hotseat (bottom of the screen).
*
Currently we don't special case for the phone UI in different orientations, even though
* the hotseat is on the side in landscape mode. This is to ensure that accessibility
* consistency is maintained across rotations.
@@ -185,7 +201,8 @@ public class FocusHelper {
return consume;
}
- DeviceProfile profile = ((Launcher) v.getContext()).getDeviceProfile();
+ final Launcher launcher = (Launcher) v.getContext();
+ final DeviceProfile profile = launcher.getDeviceProfile();
if (DEBUG) {
Log.v(TAG, String.format(
@@ -194,15 +211,13 @@ public class FocusHelper {
}
// Initialize the variables.
+ final Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
final ShortcutAndWidgetContainer hotseatParent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout hotseatLayout = (CellLayout) hotseatParent.getParent();
- Hotseat hotseat = (Hotseat) hotseatLayout.getParent();
- Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
int pageIndex = workspace.getNextPage();
int pageCount = workspace.getChildCount();
- int countX = -1;
- int countY = -1;
int iconIndex = hotseatParent.indexOfChild(v);
int iconRank = ((CellLayout.LayoutParams) hotseatLayout.getShortcutsAndWidgets()
.getChildAt(iconIndex).getLayoutParams()).cellX;
@@ -221,51 +236,91 @@ public class FocusHelper {
if (keyCode == KeyEvent.KEYCODE_DPAD_UP &&
!profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
- true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank,
- iconRank == profile.inv.hotseatAllAppsRank /* include all apps icon */);
+ matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+ true /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
iconIndex += iconParent.getChildCount();
- countX = iconLayout.getCountX();
- countY = iconLayout.getCountY() + hotseatLayout.getCountY();
parent = iconParent;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT &&
profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout,
- false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank,
- iconRank == profile.inv.hotseatAllAppsRank /* include all apps icon */);
+ matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+ false /* hotseat horizontal */, profile.inv.hotseatAllAppsRank);
iconIndex += iconParent.getChildCount();
- countX = iconLayout.getCountX() + hotseatLayout.getCountX();
- countY = iconLayout.getCountY();
parent = iconParent;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
- }else {
+ } 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.
matrix = FocusLogic.createSparseMatrix(hotseatLayout);
- countX = hotseatLayout.getCountX();
- countY = hotseatLayout.getCountY();
parent = hotseatParent;
}
// Process the focus.
- int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
- countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
+ pageCount, Utilities.isRtl(v.getResources()));
View newIcon = null;
- if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
- newIcon = parent.getChildAt(0);
- // TODO(hyunyoungs): handle cases where the child is not an icon but
- // a folder or a widget.
- workspace.snapToPage(pageIndex + 1);
+ switch (newIconIndex) {
+ case FocusLogic.NEXT_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ newIcon = parent.getChildAt(0);
+ // TODO(hyunyoungs): handle cases where the child is not an icon but
+ // a folder or a widget.
+ workspace.snapToPage(pageIndex + 1);
+ break;
+ case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(0);
+ // TODO(hyunyoungs): handle cases where the child is not an icon but
+ // a folder or a widget.
+ workspace.snapToPage(pageIndex - 1);
+ break;
+ case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = parent.getChildAt(parent.getChildCount() - 1);
+ // TODO(hyunyoungs): handle cases where the child is not an icon but
+ // a folder or a widget.
+ workspace.snapToPage(pageIndex - 1);
+ break;
+ case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ // Go to the previous page but keep the focus on the same hotseat icon.
+ workspace.snapToPage(pageIndex - 1);
+ // If the page we are going to is fullscreen, have it take the focus from hotseat.
+ CellLayout prevPage = (CellLayout) workspace.getPageAt(pageIndex - 1);
+ boolean isPrevPageFullscreen = ((CellLayout.LayoutParams) prevPage
+ .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
+ if (isPrevPageFullscreen) {
+ workspace.getPageAt(pageIndex - 1).requestFocus();
+ }
+ break;
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ case FocusLogic.NEXT_PAGE_RIGHT_COLUMN:
+ // Go to the next page but keep the focus on the same hotseat icon.
+ workspace.snapToPage(pageIndex + 1);
+ // If the page we are going to is fullscreen, have it take the focus from hotseat.
+ CellLayout nextPage = (CellLayout) workspace.getPageAt(pageIndex + 1);
+ boolean isNextPageFullscreen = ((CellLayout.LayoutParams) nextPage
+ .getShortcutsAndWidgets().getChildAt(0).getLayoutParams()).isFullscreen;
+ if (isNextPageFullscreen) {
+ workspace.getPageAt(pageIndex + 1).requestFocus();
+ }
+ break;
}
if (parent == iconParent && newIconIndex >= iconParent.getChildCount()) {
newIconIndex -= iconParent.getChildCount();
}
if (parent != null) {
- if (newIcon == null && newIconIndex >=0) {
+ if (newIcon == null && newIconIndex >= 0) {
newIcon = parent.getChildAt(newIconIndex);
}
if (newIcon != null) {
@@ -301,11 +356,10 @@ public class FocusHelper {
final ViewGroup tabs = (ViewGroup) dragLayer.findViewById(R.id.search_drop_target_bar);
final Hotseat hotseat = (Hotseat) dragLayer.findViewById(R.id.hotseat);
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
final int iconIndex = parent.indexOfChild(v);
final int pageIndex = workspace.indexOfChild(iconLayout);
final int pageCount = workspace.getChildCount();
- int countX = iconLayout.getCountX();
- int countY = iconLayout.getCountY();
CellLayout hotseatLayout = (CellLayout) hotseat.getChildAt(0);
ShortcutAndWidgetContainer hotseatParent = hotseatLayout.getShortcutsAndWidgets();
@@ -315,27 +369,30 @@ public class FocusHelper {
// to take a user to the hotseat. For other dpad navigation, do not use the matrix extended
// with the hotseat.
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, true /* horizontal */,
- profile.inv.hotseatAllAppsRank,
- !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
- countY = countY + 1;
+ matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+ true /* horizontal */, profile.inv.hotseatAllAppsRank);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
profile.isVerticalBarLayout()) {
- matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, false /* horizontal */,
- profile.inv.hotseatAllAppsRank,
- !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
- countX = countX + 1;
- } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
- workspace.removeWorkspaceItem(v);
- return consume;
+ matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout,
+ false /* horizontal */, profile.inv.hotseatAllAppsRank);
+ } 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);
}
// Process the focus.
- int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX,
- countY, matrix, iconIndex, pageIndex, pageCount, Utilities.isRtl(v.getResources()));
+ int newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, iconIndex, pageIndex,
+ pageCount, Utilities.isRtl(v.getResources()));
+ boolean isRtl = Utilities.isRtl(v.getResources());
View newIcon = null;
+ CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex);
switch (newIconIndex) {
case FocusLogic.NOOP:
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
@@ -350,32 +407,37 @@ public class FocusHelper {
}
int row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
- workspace.snapToPage(newPageIndex);
if (parent != null) {
- workspace.snapToPage(newPageIndex);
iconLayout = (CellLayout) parent.getParent();
- matrix = FocusLogic.createSparseMatrix(iconLayout,
- iconLayout.getCountX(), row);
- newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
- matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
- Utilities.isRtl(v.getResources()));
- newIcon = parent.getChildAt(newIconIndex);
+ matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout,
+ iconLayout.getCountX(), row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
+ newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
+ if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
+ newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
+ isRtl);
+ } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
+ newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
+ isRtl);
+ } else {
+ newIcon = parent.getChildAt(newIconIndex);
+ }
}
break;
case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
- newIcon = parent.getChildAt(0);
- workspace.snapToPage(pageIndex - 1);
+ workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
+ newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
+ if (newIcon == null) {
+ // Check the hotseat if no focusable item was found on the workspace.
+ newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
+ workspace.snapToPage(pageIndex - 1);
+ }
break;
case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
- newIcon = parent.getChildAt(parent.getChildCount() - 1);
- workspace.snapToPage(pageIndex - 1);
+ newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex, isRtl);
break;
case FocusLogic.NEXT_PAGE_FIRST_ITEM:
- parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
- newIcon = parent.getChildAt(0);
- workspace.snapToPage(pageIndex + 1);
+ newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex, isRtl);
break;
case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
case FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN:
@@ -383,24 +445,37 @@ public class FocusHelper {
if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LEFT_COLUMN) {
newPageIndex = pageIndex - 1;
}
- workspace.snapToPage(newPageIndex);
row = ((CellLayout.LayoutParams) v.getLayoutParams()).cellY;
parent = getCellLayoutChildrenForIndex(workspace, newPageIndex);
if (parent != null) {
- workspace.snapToPage(newPageIndex);
iconLayout = (CellLayout) parent.getParent();
- matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
- newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY,
- matrix, FocusLogic.PIVOT, newPageIndex, pageCount,
- Utilities.isRtl(v.getResources()));
- newIcon = parent.getChildAt(newIconIndex);
+ matrix = FocusLogic.createSparseMatrixWithPivotColumn(iconLayout, -1, row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, matrix, FocusLogic.PIVOT,
+ newPageIndex, pageCount, Utilities.isRtl(v.getResources()));
+ if (newIconIndex == FocusLogic.NEXT_PAGE_FIRST_ITEM) {
+ newIcon = handleNextPageFirstItem(workspace, hotseatLayout, pageIndex,
+ isRtl);
+ } else if (newIconIndex == FocusLogic.PREVIOUS_PAGE_LAST_ITEM) {
+ newIcon = handlePreviousPageLastItem(workspace, hotseatLayout, pageIndex,
+ isRtl);
+ } else {
+ newIcon = parent.getChildAt(newIconIndex);
+ }
}
break;
case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
- newIcon = parent.getChildAt(0);
+ newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
+ if (newIcon == null) {
+ // Check the hotseat if no focusable item was found on the workspace.
+ newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
+ }
break;
case FocusLogic.CURRENT_PAGE_LAST_ITEM:
- newIcon = parent.getChildAt(parent.getChildCount() - 1);
+ newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
+ if (newIcon == null) {
+ // Check the hotseat if no focusable item was found on the workspace.
+ newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout, isRtl);
+ }
break;
default:
// current page, some item.
@@ -457,4 +532,81 @@ public class FocusHelper {
break;
}
}
+
+ /**
+ * 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) {
+ return null;
+ }
+ CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex - 1);
+ View newIcon = getFirstFocusableIconInReverseReadingOrder(workspaceLayout, isRtl);
+ if (newIcon == null) {
+ // Check the hotseat if no focusable item was found on the workspace.
+ newIcon = getFirstFocusableIconInReverseReadingOrder(hotseatLayout,isRtl);
+ workspace.snapToPage(pageIndex - 1);
+ }
+ return newIcon;
+ }
+
+ private static View handleNextPageFirstItem(Workspace workspace, CellLayout hotseatLayout,
+ int pageIndex, boolean isRtl) {
+ if (pageIndex + 1 >= workspace.getPageCount()) {
+ return null;
+ }
+ CellLayout workspaceLayout = (CellLayout) workspace.getChildAt(pageIndex + 1);
+ View newIcon = getFirstFocusableIconInReadingOrder(workspaceLayout, isRtl);
+ if (newIcon == null) {
+ // Check the hotseat if no focusable item was found on the workspace.
+ newIcon = getFirstFocusableIconInReadingOrder(hotseatLayout, isRtl);
+ workspace.snapToPage(pageIndex + 1);
+ }
+ return newIcon;
+ }
+
+ private static View getFirstFocusableIconInReadingOrder(CellLayout cellLayout, boolean isRtl) {
+ View icon;
+ int countX = cellLayout.getCountX();
+ for (int y = 0; y < cellLayout.getCountY(); y++) {
+ int increment = isRtl ? -1 : 1;
+ for (int x = isRtl ? countX - 1 : 0; 0 <= x && x < countX; x += increment) {
+ if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
+ return icon;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static View getFirstFocusableIconInReverseReadingOrder(CellLayout cellLayout,
+ boolean isRtl) {
+ View icon;
+ int countX = cellLayout.getCountX();
+ for (int y = cellLayout.getCountY() - 1; y >= 0; y--) {
+ int increment = isRtl ? 1 : -1;
+ for (int x = isRtl ? 0 : countX - 1; 0 <= x && x < countX; x += increment) {
+ if ((icon = cellLayout.getChildAt(x, y)) != null && icon.isFocusable()) {
+ return icon;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java
index ecf93e4b3d..58b38ebcd2 100644
--- a/src/com/android/launcher3/FocusIndicatorView.java
+++ b/src/com/android/launcher3/FocusIndicatorView.java
@@ -30,6 +30,7 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
// It can be any number >0. The view is resized using scaleX and scaleY.
static final int DEFAULT_LAYOUT_SIZE = 100;
+
private static final float MIN_VISIBLE_ALPHA = 0.2f;
private static final long ANIM_DURATION = 150;
@@ -41,6 +42,7 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
private View mLastFocusedView;
private boolean mInitiated;
+ private final OnFocusChangeListener mHideIndicatorOnFocusListener;
private Pair mPendingCall;
@@ -52,6 +54,16 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
super(context, attrs);
setAlpha(0);
setBackgroundColor(getResources().getColor(R.color.focused_background));
+
+ mHideIndicatorOnFocusListener = new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ endCurrentAnimation();
+ setAlpha(0);
+ }
+ }
+ };
}
@Override
@@ -66,6 +78,13 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen
}
}
+ /**
+ * Sets the alpha of this FocusIndicatorView to 0 when a view with this listener receives focus.
+ */
+ public View.OnFocusChangeListener getHideIndicatorOnFocusListener() {
+ return mHideIndicatorOnFocusListener;
+ }
+
@Override
public void onFocusChange(View v, boolean hasFocus) {
mPendingCall = null;
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index c1aa35669b..471c324fe4 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -57,6 +57,7 @@ import com.android.launcher3.FolderInfo.FolderListener;
import com.android.launcher3.UninstallDropTarget.UninstallSource;
import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.UiThreadCircularReveal;
@@ -318,9 +319,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
String.format(getContext().getString(R.string.folder_renamed), newTitle));
}
- // In order to clear the focus from the text field, we set the focus on ourself. This
- // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
- requestFocus();
+
+ // 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);
mIsEditingName = false;
@@ -414,13 +416,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
/**
* Creates a new UserFolder, inflated from R.layout.user_folder.
*
- * @param context The application's context.
+ * @param launcher The main activity.
*
* @return A new UserFolder.
*/
@SuppressLint("InflateParams")
static Folder fromXml(Launcher launcher) {
- return (Folder) launcher.getLayoutInflater().inflate(R.layout.user_folder, null);
+ return (Folder) launcher.getLayoutInflater().inflate(
+ FeatureFlags.LAUNCHER3_ICON_NORMALIZATION
+ ? R.layout.user_folder_icon_normalized : R.layout.user_folder, null);
}
/**
@@ -451,6 +455,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mContent.snapToPageImmediately(0);
}
+ // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
+ // leads to an consistent state if you drag out of the folder and drag back in without
+ // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
+ mDeleteFolderOnDropCompleted = false;
+
Animator openFolderAnim = null;
final Runnable onCompleteRunnable;
if (!Utilities.ATLEAST_LOLLIPOP) {
@@ -541,10 +550,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void onAnimationEnd(Animator animation) {
mState = STATE_OPEN;
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
-
+ onCompleteRunnable.run();
mContent.setFocusOnFirstChild();
}
});
@@ -639,9 +645,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
oa.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- onCloseComplete();
setLayerType(LAYER_TYPE_NONE, null);
- mState = STATE_SMALL;
+ close(true);
}
@Override
public void onAnimationStart(Animator animation) {
@@ -655,6 +660,34 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
oa.start();
}
+ public void close(boolean wasAnimated) {
+ // TODO: Clear all active animations.
+ DragLayer parent = (DragLayer) getParent();
+ if (parent != null) {
+ parent.removeView(this);
+ }
+ mDragController.removeDropTarget(this);
+ clearFocus();
+ if (wasAnimated) {
+ mFolderIcon.requestFocus();
+ }
+
+ if (mRearrangeOnClose) {
+ rearrangeChildren();
+ mRearrangeOnClose = false;
+ }
+ if (getItemCount() <= 1) {
+ if (!mDragInProgress && !mSuppressFolderDeletion) {
+ replaceFolderWithFinalItem();
+ } else if (mDragInProgress) {
+ mDeleteFolderOnDropCompleted = true;
+ }
+ }
+ mSuppressFolderDeletion = false;
+ clearDragInfo();
+ mState = STATE_SMALL;
+ }
+
public boolean acceptDrop(DragObject d) {
final ItemInfo item = (ItemInfo) d.dragInfo;
final int itemType = item.itemType;
@@ -706,6 +739,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
mReorderAlarm.setAlarm(REORDER_DELAY);
mPrevTargetRank = mTargetRank;
+
+ if (d.stateAnnouncer != null) {
+ d.stateAnnouncer.announce(getContext().getString(R.string.move_to_position,
+ mTargetRank + 1));
+ }
}
float x = r[0];
@@ -867,7 +905,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
// Show the animation, next time something is added to the folder.
mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher);
}
-
}
@Override
@@ -1082,63 +1119,42 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return mContent.getItemCount();
}
- @Thunk void onCloseComplete() {
- DragLayer parent = (DragLayer) getParent();
- if (parent != null) {
- parent.removeView(this);
- }
- mDragController.removeDropTarget((DropTarget) this);
- clearFocus();
- mFolderIcon.requestFocus();
-
- if (mRearrangeOnClose) {
- rearrangeChildren();
- mRearrangeOnClose = false;
- }
- if (getItemCount() <= 1) {
- if (!mDragInProgress && !mSuppressFolderDeletion) {
- replaceFolderWithFinalItem();
- } else if (mDragInProgress) {
- mDeleteFolderOnDropCompleted = true;
- }
- }
- mSuppressFolderDeletion = false;
- clearDragInfo();
- }
-
@Thunk void replaceFolderWithFinalItem() {
// Add the last remaining child to the workspace in place of the folder
Runnable onCompleteRunnable = new Runnable() {
@Override
public void run() {
- CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
+ int itemCount = mInfo.contents.size();
+ if (itemCount <= 1) {
+ View newIcon = null;
- View child = null;
- // Move the item from the folder to the workspace, in the position of the folder
- if (getItemCount() == 1) {
- ShortcutInfo finalItem = mInfo.contents.get(0);
- child = mLauncher.createShortcut(cellLayout, finalItem);
- LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
- mInfo.screenId, mInfo.cellX, mInfo.cellY);
- }
- if (getItemCount() <= 1) {
- // Remove the folder
- LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
- if (cellLayout != null) {
- // b/12446428 -- sometimes the cell layout has already gone away?
- cellLayout.removeView(mFolderIcon);
+ if (itemCount == 1) {
+ // Move the item from the folder to the workspace, in the position of the
+ // folder
+ CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container,
+ mInfo.screenId);
+ ShortcutInfo finalItem = mInfo.contents.remove(0);
+ newIcon = mLauncher.createShortcut(cellLayout, finalItem);
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
+ mInfo.screenId, mInfo.cellX, mInfo.cellY);
}
+
+ // Remove the folder
+ mLauncher.removeItem(mFolderIcon, mInfo, true /* deleteFromDb */);
if (mFolderIcon instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) mFolderIcon);
}
- mLauncher.removeFolder(mInfo);
- }
- // We add the child after removing the folder to prevent both from existing at
- // the same time in the CellLayout. We need to add the new item with addInScreenFromBind()
- // to ensure that hotseat items are placed correctly.
- if (child != null) {
- mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
- mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+
+ if (newIcon != null) {
+ // We add the child after removing the folder to prevent both from existing
+ // at the same time in the CellLayout. We need to add the new item with
+ // addInScreenFromBind() to ensure that hotseat items are placed correctly.
+ mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container,
+ mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+
+ // Focus the newly created child
+ newIcon.requestFocus();
+ }
}
}
};
@@ -1155,15 +1171,37 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
return mDestroyed;
}
- // This method keeps track of the last item in the folder for the purposes
+ // This method keeps track of the first and last item in the folder for the purposes
// of keyboard focus
public void updateTextViewFocus() {
- View lastChild = mContent.getLastItem();
- if (lastChild != null) {
+ final View firstChild = mContent.getFirstItem();
+ final View lastChild = mContent.getLastItem();
+ if (firstChild != null && lastChild != null) {
mFolderName.setNextFocusDownId(lastChild.getId());
mFolderName.setNextFocusRightId(lastChild.getId());
mFolderName.setNextFocusLeftId(lastChild.getId());
mFolderName.setNextFocusUpId(lastChild.getId());
+ // Hitting TAB from the folder name wraps around to the first item on the current
+ // folder page, and hitting SHIFT+TAB from that item wraps back to the folder name.
+ mFolderName.setNextFocusForwardId(firstChild.getId());
+ // When clicking off the folder when editing the name, this Folder gains focus. When
+ // pressing an arrow key from that state, give the focus to the first item.
+ this.setNextFocusDownId(firstChild.getId());
+ this.setNextFocusRightId(firstChild.getId());
+ this.setNextFocusLeftId(firstChild.getId());
+ this.setNextFocusUpId(firstChild.getId());
+ // When pressing shift+tab in the above state, give the focus to the last item.
+ setOnKeyListener(new OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ boolean isShiftPlusTab = keyCode == KeyEvent.KEYCODE_TAB &&
+ event.hasModifiers(KeyEvent.META_SHIFT_ON);
+ if (isShiftPlusTab && Folder.this.isFocused()) {
+ return lastChild.requestFocus();
+ }
+ return false;
+ }
+ });
}
}
@@ -1284,7 +1322,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
rearrangeChildren();
}
if (getItemCount() <= 1) {
- replaceFolderWithFinalItem();
+ if (mInfo.opened) {
+ mLauncher.closeFolder(this, true);
+ } else {
+ replaceFolderWithFinalItem();
+ }
}
}
@@ -1326,8 +1368,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
public void onFocusChange(View v, boolean hasFocus) {
- if (v == mFolderName && hasFocus) {
- startEditingFolderName();
+ if (v == mFolderName) {
+ if (hasFocus) {
+ startEditingFolderName();
+ } else {
+ dismissEditingName();
+ }
}
}
@@ -1339,7 +1385,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
@Override
- public void fillInLaunchSourceData(Bundle sourceData) {
+ public void fillInLaunchSourceData(View v, Bundle sourceData) {
// Fill in from the folder icon's launch source provider first
Stats.LaunchSourceUtils.populateSourceDataFromAncestorProvider(mFolderIcon, sourceData);
sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER, Stats.SUB_CONTAINER_FOLDER);
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 8d534d2fef..d7b55b3a2f 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -520,7 +520,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
class PreviewItemDrawingParams {
- PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
+ PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
this.transX = transX;
this.transY = transY;
this.scale = scale;
@@ -529,7 +529,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
float transX;
float transY;
float scale;
- int overlayAlpha;
+ float overlayAlpha;
Drawable drawable;
}
@@ -561,7 +561,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
float transX = (mAvailableSpaceInPreview - scaledSize) / 2;
float totalScale = mBaselineIconScale * scale;
- final int overlayAlpha = (int) (80 * (1 - r));
+ final float overlayAlpha = (80 * (1 - r)) / 255f;
if (params == null) {
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
@@ -585,12 +585,12 @@ public class FolderIcon extends FrameLayout implements FolderListener {
d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
if (d instanceof FastBitmapDrawable) {
FastBitmapDrawable fd = (FastBitmapDrawable) d;
- int oldBrightness = fd.getBrightness();
+ float oldBrightness = fd.getBrightness();
fd.setBrightness(params.overlayAlpha);
d.draw(canvas);
fd.setBrightness(oldBrightness);
} else {
- d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
+ d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
PorterDuff.Mode.SRC_ATOP);
d.draw(canvas);
d.clearColorFilter();
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index cc9c5738aa..d503d2cf53 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -402,16 +402,28 @@ public class FolderPagedView extends PagedView {
return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
}
+ public View getFirstItem() {
+ if (getChildCount() < 1) {
+ return null;
+ }
+ ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+ if (mGridCountX > 0) {
+ return currContainer.getChildAt(0, 0);
+ } else {
+ return currContainer.getChildAt(0);
+ }
+ }
+
public View getLastItem() {
if (getChildCount() < 1) {
return null;
}
- ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets();
- int lastRank = lastContainer.getChildCount() - 1;
+ ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+ int lastRank = currContainer.getChildCount() - 1;
if (mGridCountX > 0) {
- return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+ return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
} else {
- return lastContainer.getChildAt(lastRank);
+ return currContainer.getChildAt(lastRank);
}
}
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java
index 5ff85d664c..0d68e33b49 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/HolographicOutlineHelper.java
@@ -164,6 +164,9 @@ public class HolographicOutlineHelper {
int bitmapWidth = (int) (rect.width() * view.getScaleX());
int bitmapHeight = (int) (rect.height() * view.getScaleY());
+ if (bitmapHeight <= 0 || bitmapWidth <= 0) {
+ return null;
+ }
int key = (bitmapWidth << 16) | bitmapHeight;
Bitmap cache = mBitmapCache.get(key);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 17fdeb1dc8..3e838760ee 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -17,13 +17,13 @@
package com.android.launcher3;
import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -70,7 +70,7 @@ public class Hotseat extends FrameLayout
public void setOnLongClickListener(OnLongClickListener l) {
mContent.setOnLongClickListener(l);
}
-
+
/* Get the orientation invariant order of the item in the hotseat for persistence. */
int getOrderInHotseat(int x, int y) {
return mHasVerticalHotseat ? (mContent.getCountY() - y - 1) : x;
@@ -118,6 +118,10 @@ public class Hotseat extends FrameLayout
Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
mLauncher.resizeIconDrawable(d);
+ int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
+ Rect bounds = d.getBounds();
+ d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
+ bounds.bottom - scaleDownPx / 2);
allAppsButton.setCompoundDrawables(null, d, null, null);
allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
@@ -150,7 +154,7 @@ public class Hotseat extends FrameLayout
}
@Override
- public void fillInLaunchSourceData(Bundle sourceData) {
+ public void fillInLaunchSourceData(View v, Bundle sourceData) {
sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_HOTSEAT);
}
}
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 59ab8397d9..c72c2f9f6d 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -28,7 +28,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -45,8 +45,10 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.SQLiteCacheHelper;
import com.android.launcher3.util.Thunk;
import java.util.Collections;
@@ -116,7 +118,7 @@ public class IconCache {
mUserManager = UserManagerCompat.getInstance(mContext);
mLauncherApps = LauncherAppsCompat.getInstance(mContext);
mIconDpi = inv.fillResIconDpi;
- mIconDb = new IconDB(context);
+ mIconDb = new IconDB(context, inv.iconBitmapSize);
mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
@@ -179,15 +181,7 @@ public class IconCache {
private Bitmap makeDefaultIcon(UserHandleCompat user) {
Drawable unbadged = getFullResDefaultActivityIcon();
- Drawable d = mUserManager.getBadgedDrawableForUser(unbadged, user);
- Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
- Math.max(d.getIntrinsicHeight(), 1),
- Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(b);
- d.setBounds(0, 0, b.getWidth(), b.getHeight());
- d.draw(c);
- c.setBitmap(null);
- return b;
+ return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
}
/**
@@ -237,9 +231,9 @@ public class IconCache {
public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
removeFromMemCacheLocked(packageName, user);
long userSerial = mUserManager.getSerialNumberForUser(user);
- mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
+ mIconDb.delete(
IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[] {packageName + "/%", Long.toString(userSerial)});
+ new String[]{packageName + "/%", Long.toString(userSerial)});
}
public void updateDbIcons(Set ignorePackagesForMainUser) {
@@ -282,58 +276,65 @@ public class IconCache {
componentMap.put(app.getComponentName(), app);
}
- Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
- new String[] {IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
- IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
- IconDB.COLUMN_SYSTEM_STATE},
- IconDB.COLUMN_USER + " = ? ",
- new String[] {Long.toString(userSerial)},
- null, null, null);
-
- final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
- final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
- final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
- final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
- final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
-
HashSet itemsToRemove = new HashSet();
Stack appsToUpdate = new Stack<>();
- while (c.moveToNext()) {
- String cn = c.getString(indexComponent);
- ComponentName component = ComponentName.unflattenFromString(cn);
- PackageInfo info = pkgInfoMap.get(component.getPackageName());
- if (info == null) {
- if (!ignorePackages.contains(component.getPackageName())) {
+ Cursor c = null;
+ try {
+ c = mIconDb.query(
+ new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+ IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
+ IconDB.COLUMN_SYSTEM_STATE},
+ IconDB.COLUMN_USER + " = ? ",
+ new String[]{Long.toString(userSerial)});
+
+ final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
+ final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
+ final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
+ final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
+ final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
+
+ while (c.moveToNext()) {
+ String cn = c.getString(indexComponent);
+ ComponentName component = ComponentName.unflattenFromString(cn);
+ PackageInfo info = pkgInfoMap.get(component.getPackageName());
+ if (info == null) {
+ if (!ignorePackages.contains(component.getPackageName())) {
+ remove(component, user);
+ itemsToRemove.add(c.getInt(rowIndex));
+ }
+ continue;
+ }
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
+ // Application is not present
+ continue;
+ }
+
+ long updateTime = c.getLong(indexLastUpdate);
+ int version = c.getInt(indexVersion);
+ LauncherActivityInfoCompat app = componentMap.remove(component);
+ if (version == info.versionCode && updateTime == info.lastUpdateTime &&
+ TextUtils.equals(mSystemState, c.getString(systemStateIndex))) {
+ continue;
+ }
+ if (app == null) {
remove(component, user);
itemsToRemove.add(c.getInt(rowIndex));
+ } else {
+ appsToUpdate.add(app);
}
- continue;
}
- if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
- // Application is not present
- continue;
- }
-
- long updateTime = c.getLong(indexLastUpdate);
- int version = c.getInt(indexVersion);
- LauncherActivityInfoCompat app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime &&
- TextUtils.equals(mSystemState, c.getString(systemStateIndex))) {
- continue;
- }
- if (app == null) {
- remove(component, user);
- itemsToRemove.add(c.getInt(rowIndex));
- } else {
- appsToUpdate.add(app);
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Error reading icon cache", e);
+ // Continue updating whatever we have read so far
+ } finally {
+ if (c != null) {
+ c.close();
}
}
- c.close();
if (!itemsToRemove.isEmpty()) {
- mIconDb.getWritableDatabase().delete(IconDB.TABLE_NAME,
- Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove),
- null);
+ mIconDb.delete(
+ Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
}
// Insert remaining apps.
@@ -363,8 +364,7 @@ public class IconCache {
values.put(IconDB.COLUMN_USER, userSerial);
values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
values.put(IconDB.COLUMN_VERSION, info.versionCode);
- mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
+ mIconDb.insertOrReplace(values);
}
@Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
@@ -380,7 +380,8 @@ public class IconCache {
}
if (entry == null) {
entry = new CacheEntry();
- entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+ entry.icon = Utilities.createBadgedIconBitmap(
+ app.getIcon(mIconDpi), app.getUser(), mContext);
}
entry.title = app.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
@@ -542,7 +543,8 @@ public class IconCache {
// Check the DB first.
if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
if (info != null) {
- entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
+ entry.icon = Utilities.createBadgedIconBitmap(
+ info.getIcon(mIconDpi), info.getUser(), mContext);
} else {
if (usePackageIcon) {
CacheEntry packageEntry = getEntryForPackageLocked(
@@ -623,9 +625,8 @@ public class IconCache {
if (appInfo == null) {
throw new NameNotFoundException("ApplicationInfo is null");
}
- Drawable drawable = mUserManager.getBadgedDrawableForUser(
- appInfo.loadIcon(mPackageManager), user);
- entry.icon = Utilities.createIconBitmap(drawable, mContext);
+ entry.icon = Utilities.createBadgedIconBitmap(
+ appInfo.loadIcon(mPackageManager), user, mContext);
entry.title = appInfo.loadLabel(mPackageManager);
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
entry.isLowResIcon = false;
@@ -678,19 +679,18 @@ public class IconCache {
label, Color.TRANSPARENT);
values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
values.put(IconDB.COLUMN_USER, userSerial);
- mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
+ mIconDb.insertOrReplace(values);
}
private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
- Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
- new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
+ Cursor c = null;
+ try {
+ c = mIconDb.query(
+ new String[]{lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
IconDB.COLUMN_LABEL},
IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[] {cacheKey.componentName.flattenToString(),
- Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))},
- null, null, null);
- try {
+ new String[]{cacheKey.componentName.flattenToString(),
+ Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
if (c.moveToNext()) {
entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : null);
entry.isLowResIcon = lowRes;
@@ -704,8 +704,12 @@ public class IconCache {
}
return true;
}
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Error reading icon cache", e);
} finally {
- c.close();
+ if (c != null) {
+ c.close();
+ }
}
return false;
}
@@ -751,9 +755,9 @@ public class IconCache {
LauncherActivityInfoCompat app = mAppsToUpdate.pop();
String cn = app.getComponentName().flattenToString();
ContentValues values = updateCacheAndGetContentValues(app, true);
- mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
+ mIconDb.update(values,
IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[] {cn, Long.toString(mUserSerial)});
+ new String[]{cn, Long.toString(mUserSerial)});
mUpdatedPackages.add(app.getComponentName().getPackageName());
if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
@@ -788,9 +792,12 @@ public class IconCache {
mSystemState = Locale.getDefault().toString();
}
- private static final class IconDB extends SQLiteOpenHelper {
+ private static final class IconDB extends SQLiteCacheHelper {
private final static int DB_VERSION = 7;
+ private final static int RELEASE_VERSION = DB_VERSION +
+ (FeatureFlags.LAUNCHER3_ICON_NORMALIZATION ? 1 : 0);
+
private final static String TABLE_NAME = "icons";
private final static String COLUMN_ROWID = "rowid";
private final static String COLUMN_COMPONENT = "componentName";
@@ -802,12 +809,14 @@ public class IconCache {
private final static String COLUMN_LABEL = "label";
private final static String COLUMN_SYSTEM_STATE = "system_state";
- public IconDB(Context context) {
- super(context, LauncherFiles.APP_ICONS_DB, null, DB_VERSION);
+ public IconDB(Context context, int iconPixelSize) {
+ super(context, LauncherFiles.APP_ICONS_DB,
+ (RELEASE_VERSION << 16) + iconPixelSize,
+ TABLE_NAME);
}
@Override
- public void onCreate(SQLiteDatabase db) {
+ protected void onCreateTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
COLUMN_COMPONENT + " TEXT NOT NULL, " +
COLUMN_USER + " INTEGER NOT NULL, " +
@@ -820,25 +829,6 @@ public class IconCache {
"PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
");");
}
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- clearDB(db);
- }
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- clearDB(db);
- }
- }
-
- private void clearDB(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
- onCreate(db);
- }
}
private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 3fc8fc0a98..921e90ced8 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -82,18 +82,17 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
strings = new HashSet(strings);
}
strings.add(encoded);
- sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).commit();
+ sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
}
}
}
- public static void removeFromInstallQueue(Context context, ArrayList packageNames,
+ public static void removeFromInstallQueue(Context context, HashSet packageNames,
UserHandleCompat user) {
if (packageNames.isEmpty()) {
return;
}
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ SharedPreferences sp = Utilities.getPrefs(context);
synchronized(sLock) {
Set strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) {
@@ -111,7 +110,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
newStringsIter.remove();
}
}
- sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).commit();
+ sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
}
}
@@ -132,7 +131,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
infos.add(info);
}
}
- sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet()).commit();
+ sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet()).apply();
return infos;
}
}
@@ -145,40 +144,53 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
return;
}
-
- PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
- if (info.launchIntent == null || info.label == null) {
- if (DBG) Log.e(TAG, "Invalid install shortcut intent");
- return;
+ PendingInstallShortcutInfo info = createPendingInfo(context, data);
+ if (info != null) {
+ queuePendingShortcutInfo(info, context);
}
-
- info = convertToLauncherActivityIfPossible(info);
- queuePendingShortcutInfo(info, context);
}
- public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
+ /**
+ * @return true is the extra is either null or is of type {@param type}
+ */
+ private static boolean isValidExtraType(Intent intent, String key, Class type) {
+ Object extra = intent.getParcelableExtra(key);
+ return extra == null || type.isInstance(extra);
+ }
+
+ /**
+ * Verifies the intent and creates a {@link PendingInstallShortcutInfo}
+ */
+ private static PendingInstallShortcutInfo createPendingInfo(Context context, Intent data) {
+ if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) ||
+ !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+ Intent.ShortcutIconResource.class)) ||
+ !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
+
+ if (DBG) Log.e(TAG, "Invalid install shortcut intent");
+ return null;
+ }
+
PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
if (info.launchIntent == null || info.label == null) {
if (DBG) Log.e(TAG, "Invalid install shortcut intent");
return null;
}
- info = convertToLauncherActivityIfPossible(info);
- return info.getShortcutInfo();
+
+ return convertToLauncherActivityIfPossible(info);
}
- static void queueInstallShortcut(LauncherActivityInfoCompat info, Context context) {
- queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+ public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
+ PendingInstallShortcutInfo info = createPendingInfo(context, data);
+ return info == null ? null : info.getShortcutInfo();
}
private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
// Queue the item up for adding if launcher has not loaded properly yet
- LauncherAppState.setApplicationContext(context.getApplicationContext());
LauncherAppState app = LauncherAppState.getInstance();
boolean launcherNotLoaded = app.getModel().getCallback() == null;
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
- addToInstallQueue(sp, info);
+ addToInstallQueue(Utilities.getPrefs(context), info);
if (!mUseInstallQueue && !launcherNotLoaded) {
flushInstallQueue(context);
}
@@ -192,22 +204,21 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
flushInstallQueue(context);
}
static void flushInstallQueue(Context context) {
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ SharedPreferences sp = Utilities.getPrefs(context);
ArrayList installQueue = getAndClearInstallQueue(sp, context);
if (!installQueue.isEmpty()) {
Iterator iter = installQueue.iterator();
ArrayList addShortcuts = new ArrayList();
while (iter.hasNext()) {
final PendingInstallShortcutInfo pendingInfo = iter.next();
- final Intent intent = pendingInfo.launchIntent;
// If the intent specifies a package, make sure the package exists
String packageName = pendingInfo.getTargetPackage();
if (!TextUtils.isEmpty(packageName)) {
UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
- if (DBG) Log.d(TAG, "Ignoring shortcut for absent package:" + intent);
+ if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+ + pendingInfo.launchIntent);
continue;
}
}
@@ -352,7 +363,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
return packageName;
}
- public boolean isLuncherActivity() {
+ public boolean isLauncherActivity() {
return activityInfo != null;
}
}
@@ -395,9 +406,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
}
return new PendingInstallShortcutInfo(data, context);
- } catch (JSONException e) {
- Log.d(TAG, "Exception reading shortcut to add: " + e);
- } catch (URISyntaxException e) {
+ } catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
}
return null;
@@ -410,7 +419,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
*/
private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
PendingInstallShortcutInfo original) {
- if (original.isLuncherActivity()) {
+ if (original.isLauncherActivity()) {
// Already an activity target
return original;
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index ae204c40c3..d60132270a 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -19,7 +19,6 @@ package com.android.launcher3;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Point;
-import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;
@@ -66,25 +65,25 @@ public class InvariantDeviceProfile {
*/
public int numFolderRows;
public int numFolderColumns;
- float iconSize;
- int iconBitmapSize;
- int fillResIconDpi;
- float iconTextSize;
+ public float iconSize;
+ public int iconBitmapSize;
+ public int fillResIconDpi;
+ public float iconTextSize;
/**
* Number of icons inside the hotseat area.
*/
- float numHotseatIcons;
+ public int numHotseatIcons;
float hotseatIconSize;
int defaultLayoutId;
// Derived invariant properties
- int hotseatAllAppsRank;
+ public int hotseatAllAppsRank;
DeviceProfile landscapeProfile;
DeviceProfile portraitProfile;
- InvariantDeviceProfile() {
+ public InvariantDeviceProfile() {
}
public InvariantDeviceProfile(InvariantDeviceProfile p) {
@@ -95,7 +94,7 @@ public class InvariantDeviceProfile {
}
InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc, int maapc,
- float is, float its, float hs, float his, int dlId) {
+ float is, float its, int hs, float his, int dlId) {
// Ensure that we have an odd number of hotseat items (since we need to place all apps)
if (hs % 2 == 0) {
throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
@@ -116,7 +115,7 @@ public class InvariantDeviceProfile {
defaultLayoutId = dlId;
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ @TargetApi(23)
InvariantDeviceProfile(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
@@ -174,9 +173,9 @@ public class InvariantDeviceProfile {
// width, height, #rows, #columns, #folder rows, #folder columns,
// iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
- 255, 300, 2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
+ 255, 300, 2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
- 255, 400, 3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
+ 255, 400, 3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
275, 420, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
@@ -184,9 +183,9 @@ public class InvariantDeviceProfile {
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
- 335, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
- predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
359, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
+ predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
+ 335, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
406, 694, 5, 5, 4, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
// The tablet profile is odd in that the landscape orientation
@@ -195,9 +194,9 @@ public class InvariantDeviceProfile {
575, 904, 5, 6, 4, 5, 4, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6));
// Larger tablet profiles always have system bars on the top & bottom
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
- 727, 1207, 5, 6, 4, 5, 4, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6));
+ 727, 1207, 5, 6, 4, 5, 4, 76, 14.4f, 7, 76, R.xml.default_workspace_5x6));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
- 1527, 2527, 7, 7, 6, 6, 4, 100, 20, 7, 72, R.xml.default_workspace_4x4));
+ 1527, 2527, 7, 7, 6, 6, 4, 100, 20, 7, 72, R.xml.default_workspace_5x6));
return predefinedDeviceProfiles;
}
@@ -252,8 +251,8 @@ public class InvariantDeviceProfile {
ArrayList pointsByNearness = points;
Collections.sort(pointsByNearness, new Comparator() {
public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
- return (int) (dist(width, height, a.minWidthDps, a.minHeightDps)
- - dist(width, height, b.minWidthDps, b.minHeightDps));
+ return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
+ dist(width, height, b.minWidthDps, b.minHeightDps));
}
});
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index f7e0ea488e..c7a6807190 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -197,4 +197,11 @@ public class ItemInfo {
+ " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+ " user=" + user + ")";
}
+
+ /**
+ * Whether this item is disabled.
+ */
+ public boolean isDisabled() {
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d6bc07c5f9..ff120f7fd1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -52,6 +52,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
@@ -72,7 +73,6 @@ import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.Log;
import android.view.Display;
-import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -85,13 +85,11 @@ import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
-import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Advanceable;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -99,6 +97,7 @@ import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.PagedView.PageSwitchListener;
import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.DefaultAppSearchController;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
@@ -107,6 +106,7 @@ import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -120,11 +120,11 @@ import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* Default launcher application.
@@ -136,7 +136,7 @@ public class Launcher extends Activity
static final boolean LOGD = false;
static final boolean PROFILE_STARTUP = false;
- static final boolean DEBUG_WIDGETS = true;
+ static final boolean DEBUG_WIDGETS = false;
static final boolean DEBUG_STRICT_MODE = false;
static final boolean DEBUG_RESUME_TIME = false;
static final boolean DEBUG_DUMP_LOG = false;
@@ -195,8 +195,6 @@ public class Launcher extends Activity
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
- // Type: int[]
- private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
@@ -205,9 +203,6 @@ public class Launcher extends Activity
static final String ACTION_FIRST_LOAD_COMPLETE =
"com.android.launcher3.action.FIRST_LOAD_COMPLETE";
- public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
- public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
-
private static final String QSB_WIDGET_ID = "qsb_widget_id";
private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
@@ -230,9 +225,6 @@ public class Launcher extends Activity
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final int ACTIVITY_START_DELAY = 1000;
- private HashMap mItemIdToViewId = new HashMap();
- private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
-
// How long to wait before the new-shortcut animation automatically pans the workspace
private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@@ -248,7 +240,8 @@ public class Launcher extends Activity
private View mPageIndicators;
@Thunk DragLayer mDragLayer;
private DragController mDragController;
- private View mWeightWatcher;
+
+ public View mWeightWatcher;
private AppWidgetManagerCompat mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
@@ -348,6 +341,8 @@ public class Launcher extends Activity
private DeviceProfile mDeviceProfile;
+ private boolean mMoveToDefaultScreenFromNewIntent;
+
// This is set to the view that launched the activity that navigated the user away from
// launcher. Since there is no callback for when the activity has finished launching, enable
// the press state and keep this reference to reset the press state when we return to launcher.
@@ -356,10 +351,9 @@ public class Launcher extends Activity
protected static HashMap sCustomAppWidgets =
new HashMap();
- private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
static {
- if (ENABLE_CUSTOM_WIDGET_TEST) {
- sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
+ if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) {
+ TestingUtils.addDummyWidget(sCustomAppWidgets);
}
}
@@ -425,17 +419,15 @@ public class Launcher extends Activity
super.onCreate(savedInstanceState);
- LauncherAppState.setApplicationContext(getApplicationContext());
LauncherAppState app = LauncherAppState.getInstance();
// Load configuration-specific DeviceProfile
mDeviceProfile = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ?
- app.getInvariantDeviceProfile().landscapeProfile
- : app.getInvariantDeviceProfile().portraitProfile;
+ app.getInvariantDeviceProfile().landscapeProfile
+ : app.getInvariantDeviceProfile().portraitProfile;
- mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
- Context.MODE_PRIVATE);
+ mSharedPrefs = Utilities.getPrefs(this);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();
@@ -463,6 +455,8 @@ public class Launcher extends Activity
setContentView(R.layout.launcher);
+ app.getInvariantDeviceProfile().landscapeProfile.setSearchBarHeight(getSearchBarHeight());
+ app.getInvariantDeviceProfile().portraitProfile.setSearchBarHeight(getSearchBarHeight());
setupViews();
mDeviceProfile.layout(this);
@@ -494,11 +488,11 @@ public class Launcher extends Activity
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(mCloseSystemDialogsReceiver, filter);
- mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
+ 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.
if (!mRotationEnabled) {
- mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false);
+ mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
}
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
@@ -656,34 +650,12 @@ public class Launcher extends Activity
return !isWorkspaceLoading();
}
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- public static int generateViewId() {
- if (Utilities.ATLEAST_JB_MR1) {
- return View.generateViewId();
- } else {
- // View.generateViewId() is not available. The following fallback logic is a copy
- // of its implementation.
- for (;;) {
- final int result = sNextGeneratedId.get();
- // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
- int newValue = result + 1;
- if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
- if (sNextGeneratedId.compareAndSet(result, newValue)) {
- return result;
- }
- }
- }
- }
-
public int getViewIdForItem(ItemInfo info) {
- // This cast is safe given the > 2B range for int.
- int itemId = (int) info.id;
- if (mItemIdToViewId.containsKey(itemId)) {
- return mItemIdToViewId.get(itemId);
- }
- int viewId = generateViewId();
- mItemIdToViewId.put(itemId, viewId);
- return viewId;
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ // This cast is safe as long as the id < 0x00FFFFFF
+ // Since we jail all the dynamically generated views, there should be no clashes
+ // with any other views.
+ return (int) info.id;
}
/**
@@ -751,6 +723,9 @@ public class Launcher extends Activity
return;
} else if (requestCode == REQUEST_PICK_WALLPAPER) {
if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
+ // User could have free-scrolled between pages before picking a wallpaper; make sure
+ // we move to the closest one now to avoid visual jump.
+ mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
showWorkspace(false);
}
return;
@@ -927,8 +902,7 @@ public class Launcher extends Activity
}
@Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
- CellLayout cellLayout =
- (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+ CellLayout cellLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
Runnable onCompleteRunnable = null;
int animationType = 0;
@@ -965,6 +939,9 @@ public class Launcher extends Activity
protected void onStop() {
super.onStop();
FirstFrameAnimatorHelper.setIsVisible(false);
+ if (Utilities.isNycOrAbove()) {
+ mAppWidgetHost.stopListening();
+ }
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStop();
@@ -975,6 +952,9 @@ public class Launcher extends Activity
protected void onStart() {
super.onStart();
FirstFrameAnimatorHelper.setIsVisible(true);
+ if (Utilities.isNycOrAbove()) {
+ mAppWidgetHost.startListening();
+ }
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStart();
@@ -1062,21 +1042,30 @@ public class Launcher extends Activity
// Consequently, the widgets will be inflated in the orientation of the foreground activity
// (framework issue). On resuming, we ensure that any widgets are inflated for the current
// orientation.
- getWorkspace().reinflateWidgetsIfNecessary();
+ if (!isWorkspaceLoading()) {
+ getWorkspace().reinflateWidgetsIfNecessary();
+ }
reinflateQSBIfNecessary();
if (DEBUG_RESUME_TIME) {
Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
}
- if (mWorkspace.getCustomContentCallbacks() != null) {
+ // We want to suppress callbacks about CustomContent being shown if we have just received
+ // onNewIntent while the user was present within launcher. In that case, we post a call
+ // to move the user to the main screen (which will occur after onResume). We don't want to
+ // have onHide (from onPause), then onShow, then onHide again, which we get if we don't
+ // suppress here.
+ if (mWorkspace.getCustomContentCallbacks() != null
+ && !mMoveToDefaultScreenFromNewIntent) {
// If we are resuming and the custom content is the current page, we call onShow().
- // It is also poassible that onShow will instead be called slightly after first layout
+ // It is also possible that onShow will instead be called slightly after first layout
// if PagedView#setRestorePage was set to the custom content page in onCreate().
if (mWorkspace.isOnOrMovingToCustomContent()) {
mWorkspace.getCustomContentCallbacks().onShow(true);
}
}
+ mMoveToDefaultScreenFromNewIntent = false;
updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
mWorkspace.onResume();
@@ -1222,7 +1211,7 @@ public class Launcher extends Activity
} else {
// On devices with a locked orientation, we will at least have the allow rotation
// setting.
- return !Utilities.isRotationAllowedForDevice(this);
+ return !getResources().getBoolean(R.bool.allow_rotation);
}
}
@@ -1291,6 +1280,29 @@ public class Launcher extends Activity
return handled;
}
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ 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();
+
+ // Stop resizing any widgets
+ mWorkspace.exitWidgetResizeMode();
+
+ // Show the overview mode if we are on the workspace
+ if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() &&
+ !mWorkspace.isSwitchingState()) {
+ mOverviewPanel.requestFocus();
+ showOverviewMode(true, true /* requestButtonFocus */);
+ }
+ }
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
private String getTypedText() {
return mDefaultKeySsb.toString();
}
@@ -1322,7 +1334,6 @@ public class Launcher extends Activity
*
* @param savedState The previous state.
*/
- @SuppressWarnings("unchecked")
private void restoreState(Bundle savedState) {
if (savedState == null) {
return;
@@ -1358,9 +1369,6 @@ public class Launcher extends Activity
setWaitingForResult(true);
mRestoring = true;
}
-
- mItemIdToViewId = (HashMap)
- savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
}
/**
@@ -1389,45 +1397,8 @@ public class Launcher extends Activity
mHotseat.setOnLongClickListener(this);
}
- mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
- mWidgetsButton = findViewById(R.id.widget_button);
- mWidgetsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!mWorkspace.isSwitchingState()) {
- onClickAddWidgetButton(arg0);
- }
- }
- });
- mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View wallpaperButton = findViewById(R.id.wallpaper_button);
- wallpaperButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!mWorkspace.isSwitchingState()) {
- onClickWallpaperPicker(arg0);
- }
- }
- });
- wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
-
- View settingsButton = findViewById(R.id.settings_button);
- if (hasSettings()) {
- settingsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!mWorkspace.isSwitchingState()) {
- onClickSettingsButton(arg0);
- }
- }
- });
- settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
- } else {
- settingsButton.setVisibility(View.GONE);
- }
-
- mOverviewPanel.setAlpha(0f);
+ // Setup the overview panel
+ setupOverviewPanel();
// Setup the workspace
mWorkspace.setHapticFeedbackEnabled(false);
@@ -1445,7 +1416,7 @@ public class Launcher extends Activity
if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
} else {
- mAppsView.setSearchBarController(mAppsView.newDefaultAppSearchController());
+ mAppsView.setSearchBarController(new DefaultAppSearchController());
}
// Setup the drag controller (drop targets have to be added in reverse order in priority)
@@ -1458,22 +1429,69 @@ public class Launcher extends Activity
mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
}
- if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
- Log.v(TAG, "adding WeightWatcher");
- mWeightWatcher = new WeightWatcher(this);
- mWeightWatcher.setAlpha(0.5f);
- ((FrameLayout) mLauncherView).addView(mWeightWatcher,
- new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT,
- Gravity.BOTTOM)
- );
-
- boolean show = shouldShowWeightWatcher();
- mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
+ if (TestingUtils.MEMORY_DUMP_ENABLED) {
+ TestingUtils.addWeightWatcher(this);
}
}
+ 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() {
+ @Override
+ public void onClick(View view) {
+ if (!mWorkspace.isSwitchingState()) {
+ onClickWallpaperPicker(view);
+ }
+ }
+ });
+ wallpaperButton.setOnLongClickListener(performClickOnLongClick);
+ wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+ // Bind widget button actions
+ mWidgetsButton = findViewById(R.id.widget_button);
+ mWidgetsButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!mWorkspace.isSwitchingState()) {
+ onClickAddWidgetButton(view);
+ }
+ }
+ });
+ mWidgetsButton.setOnLongClickListener(performClickOnLongClick);
+ mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+ // Bind settings actions
+ View settingsButton = findViewById(R.id.settings_button);
+ boolean hasSettings = hasSettings();
+ if (hasSettings) {
+ settingsButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!mWorkspace.isSwitchingState()) {
+ onClickSettingsButton(view);
+ }
+ }
+ });
+ settingsButton.setOnLongClickListener(performClickOnLongClick);
+ settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
+ } else {
+ settingsButton.setVisibility(View.GONE);
+ }
+
+ mOverviewPanel.setAlpha(0f);
+ }
+
/**
* Sets the all apps button. This method is called from {@link Hotseat}.
*/
@@ -1582,8 +1600,7 @@ public class Launcher extends Activity
ItemInfo info = mPendingAddInfo;
if (appWidgetInfo == null) {
- appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
- mAppWidgetManager.getAppWidgetInfo(appWidgetId));
+ appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
}
if (appWidgetInfo.isCustomWidget) {
@@ -1610,18 +1627,28 @@ public class Launcher extends Activity
// The AppWidgetHostView has already been inflated and instantiated
launcherInfo.hostView = hostView;
}
- launcherInfo.hostView.setTag(launcherInfo);
launcherInfo.hostView.setVisibility(View.VISIBLE);
- launcherInfo.notifyWidgetSizeChanged(this);
-
- mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
- info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
-
- addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
+ addAppWidgetToWorkspace(launcherInfo, appWidgetInfo, isWorkspaceLocked());
}
resetAddInfo();
}
+ private void addAppWidgetToWorkspace(LauncherAppWidgetInfo item,
+ LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
+ item.hostView.setTag(item);
+ item.onBindAppWidget(this);
+
+ item.hostView.setFocusable(true);
+ item.hostView.setOnFocusChangeListener(mFocusHandler);
+
+ mWorkspace.addInScreen(item.hostView, item.container, item.screenId,
+ item.cellX, item.cellY, item.spanX, item.spanY, insert);
+
+ if (!item.isCustomWidget()) {
+ addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
+ }
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1635,7 +1662,10 @@ public class Launcher extends Activity
// processing a multi-step drop
if (mAppsView != null && mWidgetsView != null &&
mPendingAddInfo.container == ItemInfo.NO_ID) {
- showWorkspace(false);
+ if (!showWorkspace(false)) {
+ // If we are already on the workspace, then manually reset all apps
+ mAppsView.reset();
+ }
}
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
mUserPresent = true;
@@ -1668,31 +1698,10 @@ public class Launcher extends Activity
}
registerReceiver(mReceiver, filter);
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
- setupTransparentSystemBarsForLollipop();
mAttached = true;
mVisible = true;
}
- /**
- * Sets up transparent navigation and status bars in Lollipop.
- * This method is a no-op for other platform versions.
- */
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void setupTransparentSystemBarsForLollipop() {
- if (Utilities.ATLEAST_LOLLIPOP) {
- Window window = getWindow();
- window.getAttributes().systemUiVisibility |=
- (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
- window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
- | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- window.setStatusBarColor(Color.TRANSPARENT);
- window.setNavigationBarColor(Color.TRANSPARENT);
- }
- }
-
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -1730,14 +1739,14 @@ public class Launcher extends Activity
mWorkspace.postDelayed(mBuildLayersRunnable, 500);
final ViewTreeObserver.OnDrawListener listener = this;
mWorkspace.post(new Runnable() {
- public void run() {
- if (mWorkspace != null &&
- mWorkspace.getViewTreeObserver() != null) {
- mWorkspace.getViewTreeObserver().
- removeOnDrawListener(listener);
- }
+ public void run() {
+ if (mWorkspace != null &&
+ mWorkspace.getViewTreeObserver() != null) {
+ mWorkspace.getViewTreeObserver().
+ removeOnDrawListener(listener);
}
- });
+ }
+ });
return;
}
});
@@ -1795,7 +1804,7 @@ public class Launcher extends Activity
}
});
- void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
+ private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
if (v instanceof Advanceable) {
@@ -1805,18 +1814,13 @@ public class Launcher extends Activity
}
}
- void removeWidgetToAutoAdvance(View hostView) {
+ private void removeWidgetToAutoAdvance(View hostView) {
if (mWidgetsToAdvance.containsKey(hostView)) {
mWidgetsToAdvance.remove(hostView);
updateAutoAdvanceState();
}
}
- public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
- removeWidgetToAutoAdvance(launcherInfo.hostView);
- launcherInfo.hostView = null;
- }
-
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();
@@ -1898,7 +1902,7 @@ public class Launcher extends Activity
// In all these cases, only animate if we're already on home
mWorkspace.exitWidgetResizeMode();
- closeFolder();
+ closeFolder(alreadyOnHome);
exitSpringLoadedDragMode();
// If we are already on home, then just animate back to the workspace,
@@ -1911,7 +1915,7 @@ public class Launcher extends Activity
final View v = getWindow().peekDecorView();
if (v != null && v.getWindowToken() != null) {
- InputMethodManager imm = (InputMethodManager)getSystemService(
+ InputMethodManager imm = (InputMethodManager) getSystemService(
INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
@@ -1943,10 +1947,16 @@ public class Launcher extends Activity
mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
openFolder == null && moveToDefaultScreen) {
+
+ // We use this flag to suppress noisy callbacks above custom content state
+ // from onResume.
+ mMoveToDefaultScreenFromNewIntent = true;
mWorkspace.post(new Runnable() {
@Override
public void run() {
- mWorkspace.moveToDefaultScreen(true);
+ if (mWorkspace != null) {
+ mWorkspace.moveToDefaultScreen(true);
+ }
}
});
}
@@ -1976,7 +1986,7 @@ public class Launcher extends Activity
outState.putInt(RUNTIME_STATE, mState.ordinal());
// We close any open folder since it will not be re-opened, and we need to make sure
// this state is reflected.
- closeFolder();
+ closeFolder(false);
if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
mWaitingForResult) {
@@ -1992,7 +2002,6 @@ public class Launcher extends Activity
// Save the current widgets tray?
// TODO(hyunyoungs)
- outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onSaveInstanceState(outState);
@@ -2162,15 +2171,6 @@ public class Launcher extends Activity
}
}
- public void startSearchFromAllApps(View v, Intent searchIntent, String searchQuery) {
- if (mLauncherCallbacks != null && mLauncherCallbacks.startSearchFromAllApps(searchQuery)) {
- return;
- }
-
- // If not handled, then just start the provided search intent
- startActivitySafely(v, searchIntent, null);
- }
-
public boolean isOnCustomContent() {
return mWorkspace.isOnOrMovingToCustomContent();
}
@@ -2178,22 +2178,9 @@ public class Launcher extends Activity
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- if (!isOnCustomContent()) {
- // Close any open folders
- closeFolder();
- // Stop resizing any widgets
- mWorkspace.exitWidgetResizeMode();
- if (!mWorkspace.isInOverviewMode()) {
- // Show the overview mode
- showOverviewMode(true);
- } else {
- showWorkspace(true);
- }
- }
if (mLauncherCallbacks != null) {
return mLauncherCallbacks.onPrepareOptionsMenu(menu);
}
-
return false;
}
@@ -2243,8 +2230,11 @@ public class Launcher extends Activity
mPendingAddInfo.dropPos = null;
}
- void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
+ void addAppWidgetFromDropImpl(final int appWidgetId, final ItemInfo info, final
AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
+ if (LOGD) {
+ Log.d(TAG, "Adding widget from drop");
+ }
addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
}
@@ -2352,8 +2342,14 @@ public class Launcher extends Activity
AppWidgetHostView hostView = info.boundWidget;
int appWidgetId;
if (hostView != null) {
+ // In the case where we've prebound the widget, we remove it from the DragLayer
+ if (LOGD) {
+ Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null");
+ }
+ getDragLayer().removeView(hostView);
+
appWidgetId = hostView.getAppWidgetId();
- addAppWidgetImpl(appWidgetId, info, hostView, info.info);
+ addAppWidgetFromDropImpl(appWidgetId, info, hostView, info.info);
// Clear the boundWidget so that it doesn't get destroyed.
info.boundWidget = null;
@@ -2366,7 +2362,7 @@ public class Launcher extends Activity
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
appWidgetId, info.info, options);
if (success) {
- addAppWidgetImpl(appWidgetId, info, null, info.info);
+ addAppWidgetFromDropImpl(appWidgetId, info, null, info.info);
} else {
mPendingAddWidgetInfo = info.info;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
@@ -2402,10 +2398,72 @@ public class Launcher extends Activity
return newFolder;
}
- void removeFolder(FolderInfo folder) {
+ /**
+ * Unbinds the view for the specified item, and removes the item and all its children.
+ *
+ * @param v the view being removed.
+ * @param itemInfo the {@link ItemInfo} for this view.
+ * @param deleteFromDb whether or not to delete this item from the db.
+ */
+ public boolean removeItem(View v, ItemInfo itemInfo, boolean deleteFromDb) {
+ if (itemInfo instanceof ShortcutInfo) {
+ // Remove the shortcut from the folder before removing it from launcher
+ FolderInfo folderInfo = sFolders.get(itemInfo.container);
+ if (folderInfo != null) {
+ folderInfo.remove((ShortcutInfo) itemInfo);
+ } else {
+ mWorkspace.removeWorkspaceItem(v);
+ }
+ if (deleteFromDb) {
+ LauncherModel.deleteItemFromDatabase(this, itemInfo);
+ }
+ } else if (itemInfo instanceof FolderInfo) {
+ final FolderInfo folderInfo = (FolderInfo) itemInfo;
+ unbindFolder(folderInfo);
+ mWorkspace.removeWorkspaceItem(v);
+ if (deleteFromDb) {
+ LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
+ }
+ } else if (itemInfo instanceof LauncherAppWidgetInfo) {
+ final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
+ mWorkspace.removeWorkspaceItem(v);
+ removeWidgetToAutoAdvance(widgetInfo.hostView);
+ widgetInfo.hostView = null;
+ if (deleteFromDb) {
+ deleteWidgetInfo(widgetInfo);
+ }
+
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Unbinds any launcher references to the folder.
+ */
+ private void unbindFolder(FolderInfo folder) {
sFolders.remove(folder.id);
}
+ /**
+ * Deletes the widget info and the widget id.
+ */
+ private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) {
+ final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost();
+ if (appWidgetHost != null && !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdValid()) {
+ // Deleting an app widget ID is a void call but writes to disk before returning
+ // to the caller...
+ new AsyncTask() {
+ public Void doInBackground(Void ... args) {
+ appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId);
+ return null;
+ }
+ }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+ }
+ LauncherModel.deleteItemFromDatabase(this, widgetInfo);
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -2469,6 +2527,9 @@ public class Launcher extends Activity
if (mAppWidgetHost != null) {
mAppWidgetHost.startListening();
}
+
+ // Recreate the QSB, as the widget has been reset.
+ bindSearchProviderChanged();
}
/**
@@ -2535,10 +2596,10 @@ public class Launcher extends Activity
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
if (v.isReadyForClickSetup()) {
int widgetId = info.appWidgetId;
- AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
+ LauncherAppWidgetProviderInfo appWidgetInfo =
+ mAppWidgetManager.getLauncherAppWidgetInfo(widgetId);
if (appWidgetInfo != null) {
- mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
- this, appWidgetInfo);
+ mPendingAddWidgetInfo = appWidgetInfo;
mPendingAddInfo.copyFrom(info);
mPendingAddWidgetId = widgetId;
@@ -2620,25 +2681,15 @@ public class Launcher extends Activity
final ShortcutInfo shortcut = (ShortcutInfo) tag;
if (shortcut.isDisabled != 0) {
- int error = R.string.activity_not_available;
- if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
- error = R.string.safemode_shortcut_error;
- }
- Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
- return;
- }
-
- final Intent intent = shortcut.intent;
-
- // Check for special shortcuts
- if (intent.getComponent() != null) {
- final String shortcutClass = intent.getComponent().getClassName();
-
- if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
- MemoryDumpActivity.startDump(this);
- return;
- } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
- toggleShowWeightWatcher();
+ if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SUSPENDED) != 0
+ || (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_QUIET_USER) != 0) {
+ // Launch activity anyway, framework will tell the user why the app is suspended.
+ } else {
+ int error = R.string.activity_not_available;
+ if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
+ error = R.string.safemode_shortcut_error;
+ }
+ Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
return;
}
}
@@ -2728,7 +2779,7 @@ public class Launcher extends Activity
if (openFolder != null) {
folderScreen = mWorkspace.getPageForView(openFolder);
// .. and close it
- closeFolder(openFolder);
+ closeFolder(openFolder, true);
if (folderScreen != mWorkspace.getCurrentPage()) {
// Close any folder open on the current screen
closeFolder();
@@ -2765,7 +2816,10 @@ public class Launcher extends Activity
*/
protected void onClickWallpaperPicker(View v) {
if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
- startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
+ int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
+ float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
+ startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName())
+ .putExtra(WallpaperPickerActivity.EXTRA_WALLPAPER_OFFSET, offset),
REQUEST_PICK_WALLPAPER);
if (mLauncherCallbacks != null) {
@@ -3063,9 +3117,13 @@ public class Launcher extends Activity
}
oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
oa.start();
+ if (Utilities.isPowerSaverOn(this)) {
+ // Animations are disabled in battery saver mode, so just skip to the end state.
+ oa.end();
+ }
}
- private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
+ private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) {
if (fi == null) return;
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
@@ -3091,6 +3149,9 @@ public class Launcher extends Activity
}
});
oa.start();
+ if (!animate) {
+ oa.end();
+ }
}
/**
@@ -3134,30 +3195,38 @@ public class Launcher extends Activity
}
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);
+ closeFolder(folder, animate);
}
}
- public void closeFolder(Folder folder) {
+ public void closeFolder(Folder folder, boolean animate) {
folder.getInfo().opened = false;
ViewGroup parent = (ViewGroup) folder.getParent().getParent();
if (parent != null) {
FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
- shrinkAndFadeInFolderIcon(fi);
+ shrinkAndFadeInFolderIcon(fi, animate);
if (fi != null) {
((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
}
}
- folder.animateClosed();
+ if (animate) {
+ folder.animateClosed();
+ } else {
+ folder.close(false);
+ }
- // Notify the accessibility manager that this folder "window" has disappeard and no
- // longer occludeds the workspace items
+ // Notify the accessibility manager that this folder "window" has disappeared and no
+ // longer occludes the workspace items
getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
@@ -3296,20 +3365,32 @@ public class Launcher extends Activity
}
}
- public void showWorkspace(boolean animated) {
- showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
+ /**
+ * @return whether or not the Launcher state changed.
+ */
+ public boolean showWorkspace(boolean animated) {
+ return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
}
- public void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
- showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
+ /**
+ * @return whether or not the Launcher state changed.
+ */
+ public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
+ return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
onCompleteRunnable);
}
- protected void showWorkspace(int snapToPage, boolean animated) {
- showWorkspace(snapToPage, animated, null);
+ /**
+ * @return whether or not the Launcher state changed.
+ */
+ protected boolean showWorkspace(int snapToPage, boolean animated) {
+ return showWorkspace(snapToPage, animated, null);
}
- void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) {
+ /**
+ * @return whether or not the Launcher state changed.
+ */
+ boolean showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) {
boolean changed = mState != State.WORKSPACE ||
mWorkspace.getState() != Workspace.State.NORMAL;
if (changed) {
@@ -3335,14 +3416,38 @@ public class Launcher extends Activity
getWindow().getDecorView()
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
+ return changed;
}
+ /**
+ * Shows the overview button.
+ */
void showOverviewMode(boolean animated) {
+ showOverviewMode(animated, false);
+ }
+
+ /**
+ * Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus
+ * onto one of the overview panel buttons.
+ */
+ void showOverviewMode(boolean animated, boolean requestButtonFocus) {
+ Runnable postAnimRunnable = null;
+ if (requestButtonFocus) {
+ postAnimRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Hitting the menu button when in touch mode does not trigger touch mode to
+ // be disabled, so if requested, force focus on one of the overview panel
+ // buttons.
+ mOverviewPanel.requestFocusFromTouch();
+ }
+ };
+ }
mWorkspace.setVisibility(View.VISIBLE);
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
Workspace.State.OVERVIEW,
WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
- null /* onCompleteRunnable */);
+ postAnimRunnable);
mState = State.WORKSPACE;
}
@@ -3493,12 +3598,12 @@ public class Launcher extends Activity
// TODO
}
- protected void disableVoiceButtonProxy(boolean disable) {
- // NO-OP
+ public boolean launcherCallbacksProvidesSearch() {
+ return (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch());
}
public View getOrCreateQsbBar() {
- if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
+ if (launcherCallbacksProvidesSearch()) {
return mLauncherCallbacks.getQsbBar();
}
@@ -3512,12 +3617,40 @@ public class Launcher extends Activity
opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
- SharedPreferences sp = getSharedPreferences(
- LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
- int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
+ // Determine the min and max dimensions of the widget.
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile portraitProfile = app.getInvariantDeviceProfile().portraitProfile;
+ DeviceProfile landscapeProfile = app.getInvariantDeviceProfile().landscapeProfile;
+ float density = getResources().getDisplayMetrics().density;
+ Point searchDimens = portraitProfile.getSearchBarDimensForWidgetOpts(getResources());
+ int maxHeight = (int) (searchDimens.y / density);
+ int minHeight = maxHeight;
+ int maxWidth = (int) (searchDimens.x / density);
+ int minWidth = maxWidth;
+ if (!landscapeProfile.isVerticalBarLayout()) {
+ searchDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(getResources());
+ maxHeight = (int) Math.max(maxHeight, searchDimens.y / density);
+ minHeight = (int) Math.min(minHeight, searchDimens.y / density);
+ maxWidth = (int) Math.max(maxWidth, searchDimens.x / density);
+ minWidth = (int) Math.min(minWidth, searchDimens.x / density);
+ }
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, maxWidth);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, minWidth);
+ if (LOGD) {
+ Log.d(TAG, "QSB widget options: maxHeight=" + maxHeight + " minHeight=" + minHeight
+ + " maxWidth=" + maxWidth + " minWidth=" + minWidth);
+ }
+
+ if (mLauncherCallbacks != null) {
+ opts.putAll(mLauncherCallbacks.getAdditionalSearchWidgetOptions());
+ }
+
+ int widgetId = mSharedPrefs.getInt(QSB_WIDGET_ID, -1);
AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
if (!searchProvider.provider.flattenToString().equals(
- sp.getString(QSB_WIDGET_PROVIDER, null))
+ mSharedPrefs.getString(QSB_WIDGET_PROVIDER, null))
|| (widgetInfo == null)
|| !widgetInfo.provider.equals(searchProvider.provider)) {
// A valid widget is not already bound.
@@ -3535,10 +3668,10 @@ public class Launcher extends Activity
widgetId = -1;
}
- sp.edit()
+ mSharedPrefs.edit()
.putInt(QSB_WIDGET_ID, widgetId)
.putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
- .commit();
+ .apply();
}
mAppWidgetHost.setQsbWidgetId(widgetId);
@@ -3709,30 +3842,6 @@ public class Launcher extends Activity
}
}
- private boolean shouldShowWeightWatcher() {
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
- boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
-
- return show;
- }
-
- private void toggleShowWeightWatcher() {
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
- boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
-
- show = !show;
-
- SharedPreferences.Editor editor = sp.edit();
- editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
- editor.commit();
-
- if (mWeightWatcher != null) {
- mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- }
-
public void bindAppsAdded(final ArrayList newScreens,
final ArrayList addNotAnimated,
final ArrayList addAnimated,
@@ -3775,6 +3884,7 @@ public class Launcher extends Activity
*
* Implementation of the method from LauncherModel.Callbacks.
*/
+ @Override
public void bindItems(final ArrayList shortcuts, final int start, final int end,
final boolean forceAnimateIcons) {
Runnable r = new Runnable() {
@@ -3893,6 +4003,16 @@ public class Launcher extends Activity
sFolders = folders.clone();
}
+ private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
+ PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
+ view.updateIcon(mIconCache);
+ item.hostView = view;
+ item.hostView.updateAppWidget(null);
+ item.hostView.setOnClickListener(this);
+ addAppWidgetToWorkspace(item, null, false);
+ mWorkspace.requestLayout();
+ }
+
/**
* Add the views for a widget to the workspace.
*
@@ -3908,19 +4028,31 @@ public class Launcher extends Activity
return;
}
+ if (mIsSafeModeEnabled) {
+ bindSafeModeWidget(item);
+ return;
+ }
+
final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: " + item);
}
- final Workspace workspace = mWorkspace;
- LauncherAppWidgetProviderInfo appWidgetInfo =
- LauncherModel.getProviderInfo(this, item.providerName, item.user);
+ final LauncherAppWidgetProviderInfo appWidgetInfo;
- if (!mIsSafeModeEnabled
- && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
- && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
+ if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
+ // If the provider is not ready, bind as a pending widget.
+ appWidgetInfo = null;
+ } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+ // The widget id is not valid. Try to find the widget based on the provider info.
+ appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
+ } else {
+ appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
+ }
+ // If the provider is ready, but the width is not yet restored, try to restore it.
+ if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
+ (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
if (appWidgetInfo == null) {
if (DEBUG_WIDGETS) {
Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
@@ -3932,7 +4064,7 @@ public class Launcher extends Activity
}
// If we do not have a valid id, try to bind an id.
- if ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0) {
+ if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
// Note: This assumes that the id remap broadcast is received before this step.
// If that is not the case, the id remap will be ignored and user may see the
// click to setup view.
@@ -3941,8 +4073,7 @@ public class Launcher extends Activity
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
- Bundle options = null;
- WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
+ Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -3969,44 +4100,42 @@ public class Launcher extends Activity
: LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
LauncherModel.updateItemInDatabase(this, item);
- } else if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0)
+ } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
&& (appWidgetInfo.configure == null)) {
- // If the ID is already valid, verify if we need to configure or not.
+ // The widget was marked as UI not ready, but there is no configure activity to
+ // update the UI.
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
LauncherModel.updateItemInDatabase(this, item);
}
}
- if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
- final int appWidgetId = item.appWidgetId;
+ if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
if (DEBUG_WIDGETS) {
Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
+ appWidgetInfo.provider);
}
- item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+ // Verify that we own the widget
+ if (appWidgetInfo == null) {
+ Log.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
+ deleteWidgetInfo(item);
+ return;
+ }
+
+ item.hostView = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
item.minSpanX = appWidgetInfo.minSpanX;
item.minSpanY = appWidgetInfo.minSpanY;
+ addAppWidgetToWorkspace(item, appWidgetInfo, false);
} else {
- appWidgetInfo = null;
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
mIsSafeModeEnabled);
view.updateIcon(mIconCache);
item.hostView = view;
item.hostView.updateAppWidget(null);
item.hostView.setOnClickListener(this);
+ addAppWidgetToWorkspace(item, null, false);
}
-
- item.hostView.setTag(item);
- item.onBindAppWidget(this);
-
- workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
- item.cellY, item.spanX, item.spanY, false);
- if (!item.isCustomWidget()) {
- addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
- }
-
- workspace.requestLayout();
+ mWorkspace.requestLayout();
if (DEBUG_WIDGETS) {
Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
@@ -4128,10 +4257,18 @@ public class Launcher extends Activity
return mDeviceProfile.isVerticalBarLayout();
}
+ /** Returns the search bar bounds in pixels. */
protected Rect getSearchBarBounds() {
return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
}
+ public int getSearchBarHeight() {
+ if (mLauncherCallbacks != null) {
+ return mLauncherCallbacks.getSearchBarHeight();
+ }
+ return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL;
+ }
+
public void bindSearchProviderChanged() {
if (mSearchDropTargetBar == null) {
return;
@@ -4235,7 +4372,7 @@ public class Launcher extends Activity
}
mWorkspace.removeItemsByComponentName(removedComponents, user);
// Notify the drag controller
- mDragController.onAppsRemoved(new ArrayList(), removedComponents);
+ mDragController.onAppsRemoved(new HashSet(), removedComponents);
}
}
@@ -4259,43 +4396,44 @@ public class Launcher extends Activity
}
/**
- * A package was uninstalled. We take both the super set of packageNames
+ * A package was uninstalled/updated. We take both the super set of packageNames
* in addition to specific applications to remove, the reason being that
* this can be called when a package is updated as well. In that scenario,
- * we only remove specific components from the workspace, where as
+ * we only remove specific components from the workspace and hotseat, where as
* package-removal should clear all items by package name.
- *
- * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
- * Implementation of the method from LauncherModel.Callbacks.
*/
@Override
- public void bindComponentsRemoved(final ArrayList packageNames,
- final ArrayList appInfos, final UserHandleCompat user, final int reason) {
+ public void bindWorkspaceComponentsRemoved(
+ final HashSet packageNames, final HashSet components,
+ final UserHandleCompat user) {
Runnable r = new Runnable() {
public void run() {
- bindComponentsRemoved(packageNames, appInfos, user, reason);
+ bindWorkspaceComponentsRemoved(packageNames, components, user);
}
};
if (waitUntilResume(r)) {
return;
}
+ if (!packageNames.isEmpty()) {
+ mWorkspace.removeItemsByPackageName(packageNames, user);
+ }
+ if (!components.isEmpty()) {
+ mWorkspace.removeItemsByComponentName(components, user);
+ }
+ // Notify the drag controller
+ mDragController.onAppsRemoved(packageNames, components);
- if (reason == 0) {
- HashSet removedComponents = new HashSet();
- for (AppInfo info : appInfos) {
- removedComponents.add(info.componentName);
- }
- if (!packageNames.isEmpty()) {
- mWorkspace.removeItemsByPackageName(packageNames, user);
- }
- if (!removedComponents.isEmpty()) {
- mWorkspace.removeItemsByComponentName(removedComponents, user);
- }
- // Notify the drag controller
- mDragController.onAppsRemoved(packageNames, removedComponents);
+ }
- } else {
- mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
+ @Override
+ public void bindAppInfosRemoved(final ArrayList appInfos) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindAppInfosRemoved(appInfos);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
}
// Update AllApps
@@ -4304,15 +4442,15 @@ public class Launcher extends Activity
}
}
- private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
+ private Runnable mBindWidgetModelRunnable = new Runnable() {
public void run() {
- bindAllPackages(mWidgetsModel);
+ bindWidgetsModel(mWidgetsModel);
}
};
@Override
- public void bindAllPackages(final WidgetsModel model) {
- if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
+ public void bindWidgetsModel(WidgetsModel model) {
+ if (waitUntilResume(mBindWidgetModelRunnable, true)) {
mWidgetsModel = model;
return;
}
@@ -4323,6 +4461,13 @@ public class Launcher extends Activity
}
}
+ @Override
+ public void notifyWidgetProvidersChanged() {
+ if (mWorkspace.getState().shouldUpdateWidget) {
+ mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty());
+ }
+ }
+
private int mapConfigurationOriActivityInfoOri(int configOri) {
final Display d = getWindowManager().getDefaultDisplay();
int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
@@ -4630,8 +4775,9 @@ public class Launcher extends Activity
/**
* Resizes an icon drawable to the correct icon size.
*/
- public void resizeIconDrawable(Drawable icon) {
+ public Drawable resizeIconDrawable(Drawable icon) {
icon.setBounds(0, 0, mDeviceProfile.iconSizePx, mDeviceProfile.iconSizePx);
+ return icon;
}
/**
@@ -4753,7 +4899,15 @@ public class Launcher extends Activity
}
return null;
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ public static List getFolderContents(View icon) {
+ if (icon instanceof FolderIcon) {
+ return ((FolderIcon) icon).getFolder().getItemsInReadingOrder();
+ } else {
+ return Collections.EMPTY_LIST;
}
}
}
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 6a248a3325..fa20f0360b 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -24,7 +24,6 @@ import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.os.Build;
import android.view.View;
-import android.view.ViewAnimationUtils;
import android.view.ViewTreeObserver;
import com.android.launcher3.util.UiThreadCircularReveal;
@@ -59,24 +58,25 @@ public class LauncherAnimUtils {
// it should be cancelled
public static void startAnimationAfterNextDraw(final Animator animator, final View view) {
view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
- private boolean mStarted = false;
- public void onDraw() {
- if (mStarted) return;
- mStarted = true;
- // Use this as a signal that the animation was cancelled
- if (animator.getDuration() == 0) {
- return;
- }
- animator.start();
+ private boolean mStarted = false;
- final ViewTreeObserver.OnDrawListener listener = this;
- view.post(new Runnable() {
- public void run() {
- view.getViewTreeObserver().removeOnDrawListener(listener);
- }
- });
+ public void onDraw() {
+ if (mStarted) return;
+ mStarted = true;
+ // Use this as a signal that the animation was cancelled
+ if (animator.getDuration() == 0) {
+ return;
}
- });
+ animator.start();
+
+ final ViewTreeObserver.OnDrawListener listener = this;
+ view.post(new Runnable() {
+ public void run() {
+ view.getViewTreeObserver().removeOnDrawListener(listener);
+ }
+ });
+ }
+ });
}
public static void onDestroyActivity() {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d87ad67e5f..e1ade10bab 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -20,13 +20,15 @@ import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.UserManager;
import android.util.Log;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
import java.lang.ref.WeakReference;
@@ -34,7 +36,6 @@ import java.lang.ref.WeakReference;
public class LauncherAppState {
private final AppFilter mAppFilter;
- private final BuildInfo mBuildInfo;
@Thunk final LauncherModel mModel;
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
@@ -79,8 +80,8 @@ public class LauncherAppState {
Log.v(Launcher.TAG, "LauncherAppState inited");
- if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
- MemoryTracker.startTrackingMe(sContext, "L");
+ if (TestingUtils.MEMORY_DUMP_ENABLED) {
+ TestingUtils.startTrackingMemory(sContext);
}
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
@@ -88,7 +89,6 @@ public class LauncherAppState {
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
- mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
@@ -100,9 +100,15 @@ public class LauncherAppState {
// For handling managed profiles
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
+ filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE);
+ filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE);
sContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(sContext).enableAndResetCache();
+ new ConfigMonitor(sContext).register();
+
+ sContext.registerReceiver(
+ new WallpaperChangedReceiver(), new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
}
/**
@@ -152,14 +158,10 @@ public class LauncherAppState {
return sLauncherProvider.get();
}
- public static String getSharedPreferencesKey() {
- return LauncherFiles.SHARED_PREFERENCES_KEY;
- }
-
public WidgetPreviewLoader getWidgetCache() {
return mWidgetCache;
}
-
+
public void onWallpaperChanged() {
mWallpaperChangedSinceLastCheck = true;
}
@@ -175,6 +177,6 @@ public class LauncherAppState {
}
public static boolean isDogfoodBuild() {
- return getInstance().mBuildInfo.isDogfoodBuild();
+ return FeatureFlags.IS_ALPHA_BUILD || FeatureFlags.IS_DEV_BUILD;
}
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 6c3a1e8963..1510558f19 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.os.DeadObjectException;
import android.os.TransactionTooLargeException;
import android.view.LayoutInflater;
import android.view.View;
@@ -69,7 +70,8 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
try {
super.startListening();
} catch (Exception e) {
- if (e.getCause() instanceof TransactionTooLargeException) {
+ if (e.getCause() instanceof TransactionTooLargeException ||
+ e.getCause() instanceof DeadObjectException) {
// We're willing to let this slide. The exception is being caused by the list of
// RemoteViews which is being passed back. The startListening relationship will
// have been established by this point, and we will end up populating the
@@ -80,12 +82,6 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
}
}
- @Override
- public void stopListening() {
- super.stopListening();
- clearViews();
- }
-
public void addProviderChangeListener(Runnable callback) {
mProviderChangeListeners.add(callback);
}
@@ -100,6 +96,10 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
callback.run();
}
}
+
+ if (Utilities.ATLEAST_MARSHMALLOW) {
+ mLauncher.notifyWidgetProvidersChanged();
+ }
}
public AppWidgetHostView createView(Context context, int appWidgetId,
@@ -125,5 +125,8 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
mLauncher, appWidget);
super.onProviderChanged(appWidgetId, info);
+ // The super method updates the dimensions of the providerInfo. Update the
+ // launcher spans accordingly.
+ info.initSpans();
}
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index cf461a5b82..418525758a 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -19,6 +19,8 @@ package com.android.launcher3;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.graphics.Rect;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -28,6 +30,8 @@ import android.widget.RemoteViews;
import com.android.launcher3.DragLayer.TouchCompleteListener;
+import java.util.ArrayList;
+
/**
* {@inheritDoc}
*/
@@ -43,6 +47,8 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
private float mSlop;
+ private boolean mChildrenFocused;
+
public LauncherAppWidgetHostView(Context context) {
super(context);
mContext = context;
@@ -51,6 +57,8 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mDragLayer = ((Launcher) context).getDragLayer();
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
+
+ setBackgroundResource(R.drawable.widget_internal_focus_bg);
}
@Override
@@ -175,6 +183,108 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
@Override
public int getDescendantFocusability() {
- return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+ return mChildrenFocused ? ViewGroup.FOCUS_BEFORE_DESCENDANTS
+ : ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mChildrenFocused && event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE
+ && event.getAction() == KeyEvent.ACTION_UP) {
+ mChildrenFocused = false;
+ requestFocus();
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) {
+ event.startTracking();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (event.isTracking()) {
+ if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) {
+ mChildrenFocused = true;
+ ArrayList focusableChildren = getFocusables(FOCUS_FORWARD);
+ focusableChildren.remove(this);
+ int childrenCount = focusableChildren.size();
+ switch (childrenCount) {
+ case 0:
+ mChildrenFocused = false;
+ break;
+ case 1: {
+ if (getTag() instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) getTag();
+ if (item.spanX == 1 && item.spanY == 1) {
+ focusableChildren.get(0).performClick();
+ mChildrenFocused = false;
+ return true;
+ }
+ }
+ // continue;
+ }
+ default:
+ focusableChildren.get(0).requestFocus();
+ return true;
+ }
+ }
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ if (gainFocus) {
+ mChildrenFocused = false;
+ dispatchChildFocus(false);
+ }
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ dispatchChildFocus(focused != null);
+ if (focused != null) {
+ focused.setFocusableInTouchMode(false);
+ }
+ }
+
+ @Override
+ public void clearChildFocus(View child) {
+ super.clearChildFocus(child);
+ dispatchChildFocus(false);
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ return mChildrenFocused;
+ }
+
+ private void dispatchChildFocus(boolean childIsFocused) {
+ // The host view's background changes when selected, to indicate the focus is inside.
+ setSelected(childIsFocused);
+ }
+
+ @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));
+ }
+ });
+ }
}
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 882f7e2026..55edf45d29 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -123,17 +123,11 @@ public class LauncherAppWidgetInfo extends ItemInfo {
*/
void onBindAppWidget(Launcher launcher) {
if (!mHasNotifiedInitialWidgetSizeChanged) {
- notifyWidgetSizeChanged(launcher);
+ AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY);
+ mHasNotifiedInitialWidgetSizeChanged = true;
}
}
- /**
- * Trigger an update callback to the widget to notify it that its size has changed.
- */
- void notifyWidgetSizeChanged(Launcher launcher) {
- AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY);
- mHasNotifiedInitialWidgetSizeChanged = true;
- }
@Override
public String toString() {
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 71a08a8536..28d805269a 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -59,7 +59,7 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo {
initSpans();
}
- private void initSpans() {
+ public void initSpans() {
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index 8eb4e63694..1703e41672 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -20,25 +20,26 @@ import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupManager;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import com.android.launcher3.model.MigrateFromRestoreTask;
+import com.android.launcher3.model.GridSizeMigrationTask;
import java.io.IOException;
public class LauncherBackupAgentHelper extends BackupAgentHelper {
- private static final String TAG = "LauncherBackupAgentHelper";
+ private static final String TAG = "LauncherBAHelper";
+
+ private static final String KEY_LAST_NOTIFIED_TIME = "backup_manager_last_notified";
private static final String LAUNCHER_DATA_PREFIX = "L";
static final boolean VERBOSE = false;
static final boolean DEBUG = false;
- private static BackupManager sBackupManager;
-
/**
* Notify the backup manager that out database is dirty.
*
@@ -47,10 +48,29 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
* @param context application context
*/
public static void dataChanged(Context context) {
- if (sBackupManager == null) {
- sBackupManager = new BackupManager(context);
+ dataChanged(context, 0);
+ }
+
+ /**
+ * Notify the backup manager that out database is dirty.
+ *
+ *
This does not force an immediate backup.
+ *
+ * @param context application context
+ * @param throttleMs duration in ms for which two consecutive calls to backup manager should
+ * not be made.
+ */
+ public static void dataChanged(Context context, long throttleMs) {
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ long now = System.currentTimeMillis();
+ long lastTime = prefs.getLong(KEY_LAST_NOTIFIED_TIME, 0);
+
+ // User can manually change the system time, which could lead to now < lastTime.
+ // Re-backup in that case, as the backup will have a wrong lastModifiedTime.
+ if (now < lastTime || now >= (lastTime + throttleMs)) {
+ BackupManager.dataChanged(context.getPackageName());
+ prefs.edit().putLong(KEY_LAST_NOTIFIED_TIME, now).apply();
}
- sBackupManager.dataChanged();
}
private LauncherBackupHelper mHelper;
@@ -91,18 +111,16 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
if (hasData && mHelper.restoreSuccessful) {
LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
- LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
+ LauncherClings.markFirstRunClingDismissed(this);
// Rank was added in v4.
if (mHelper.restoredBackupVersion <= 3) {
LauncherAppState.getLauncherProvider().updateFolderItemsRank();
}
- if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
- MigrateFromRestoreTask.markForMigration(getApplicationContext(),
- (int) mHelper.migrationCompatibleProfileData.desktopCols,
- (int) mHelper.migrationCompatibleProfileData.desktopRows,
- mHelper.widgetSizes);
+ if (GridSizeMigrationTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
+ GridSizeMigrationTask.markForMigration(getApplicationContext(),
+ mHelper.widgetSizes, mHelper.migrationCompatibleProfileData);
}
LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 48d9351ab5..ea67ac89b4 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -41,18 +41,19 @@ import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
-import com.android.launcher3.backup.BackupProtos;
-import com.android.launcher3.backup.BackupProtos.CheckedMessage;
-import com.android.launcher3.backup.BackupProtos.DeviceProfieData;
-import com.android.launcher3.backup.BackupProtos.Favorite;
-import com.android.launcher3.backup.BackupProtos.Journal;
-import com.android.launcher3.backup.BackupProtos.Key;
-import com.android.launcher3.backup.BackupProtos.Resource;
-import com.android.launcher3.backup.BackupProtos.Screen;
-import com.android.launcher3.backup.BackupProtos.Widget;
+import com.android.launcher3.backup.nano.BackupProtos;
+import com.android.launcher3.backup.nano.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.nano.BackupProtos.DeviceProfieData;
+import com.android.launcher3.backup.nano.BackupProtos.Favorite;
+import com.android.launcher3.backup.nano.BackupProtos.Journal;
+import com.android.launcher3.backup.nano.BackupProtos.Key;
+import com.android.launcher3.backup.nano.BackupProtos.Resource;
+import com.android.launcher3.backup.nano.BackupProtos.Screen;
+import com.android.launcher3.backup.nano.BackupProtos.Widget;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.model.MigrateFromRestoreTask;
+import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.Thunk;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import com.google.protobuf.nano.MessageNano;
@@ -315,14 +316,13 @@ public class LauncherBackupHelper implements BackupHelper {
return true;
}
- if (MigrateFromRestoreTask.ENABLED &&
- (oldProfile.desktopCols - currentProfile.desktopCols <= 1) &&
- (oldProfile.desktopRows - currentProfile.desktopRows <= 1)) {
- // Allow desktop migration when row and/or column count contracts by 1.
-
+ if (GridSizeMigrationTask.ENABLED) {
+ // One time migrate the workspace when launcher starts.
migrationCompatibleProfileData = initDeviceProfileData(mIdp);
migrationCompatibleProfileData.desktopCols = oldProfile.desktopCols;
migrationCompatibleProfileData.desktopRows = oldProfile.desktopRows;
+ migrationCompatibleProfileData.hotseatCount = oldProfile.hotseatCount;
+ migrationCompatibleProfileData.allappsRank = oldProfile.allappsRank;
return true;
}
return false;
@@ -661,12 +661,14 @@ public class LauncherBackupHelper implements BackupHelper {
+ getUserSelectionArg();
Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
where, null, null);
+ AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
try {
cursor.moveToPosition(-1);
while(cursor.moveToNext()) {
final long id = cursor.getLong(ID_INDEX);
final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
final ComponentName provider = ComponentName.unflattenFromString(providerName);
+
Key key = null;
String backupKey = null;
if (provider != null) {
@@ -685,11 +687,14 @@ public class LauncherBackupHelper implements BackupHelper {
} else if (backupKey != null) {
if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount);
if (backupWidgetCount < MAX_WIDGETS_PER_PASS) {
- if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
- UserHandleCompat user = UserHandleCompat.myUserHandle();
- writeRowToBackup(key, packWidget(dpi, provider, user), data);
- mKeys.add(key);
- backupWidgetCount ++;
+ LauncherAppWidgetProviderInfo widgetInfo = widgetManager
+ .getLauncherAppWidgetInfo(cursor.getInt(APPWIDGET_ID_INDEX));
+ if (widgetInfo != null) {
+ if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
+ writeRowToBackup(key, packWidget(dpi, widgetInfo), data);
+ mKeys.add(key);
+ backupWidgetCount ++;
+ }
} else {
if (VERBOSE) Log.v(TAG, "deferring widget backup " + backupKey);
// too many widgets for this pass, request another.
@@ -768,13 +773,10 @@ public class LauncherBackupHelper implements BackupHelper {
try {
Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
if (key.checksum != checkKey(key)) {
- key = null;
throw new InvalidBackupException("invalid key read from stream" + backupKey);
}
return key;
- } catch (InvalidProtocolBufferNanoException e) {
- throw new InvalidBackupException(e);
- } catch (IllegalArgumentException e) {
+ } catch (InvalidProtocolBufferNanoException | IllegalArgumentException e) {
throw new InvalidBackupException(e);
}
}
@@ -1008,16 +1010,14 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Serialize a widget for persistence, including a checksum wrapper. */
- private Widget packWidget(int dpi, ComponentName provider, UserHandleCompat user) {
- final LauncherAppWidgetProviderInfo info =
- LauncherModel.getProviderInfo(mContext, provider, user);
+ private Widget packWidget(int dpi, LauncherAppWidgetProviderInfo info) {
Widget widget = new Widget();
- widget.provider = provider.flattenToShortString();
+ widget.provider = info.provider.flattenToShortString();
widget.label = info.label;
widget.configure = info.configure != null;
if (info.icon != 0) {
widget.icon = new Resource();
- Drawable fullResIcon = mIconCache.getFullResIcon(provider.getPackageName(), info.icon);
+ Drawable fullResIcon = mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon);
Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
widget.icon.data = Utilities.flattenBitmap(icon);
widget.icon.dpi = dpi;
@@ -1026,7 +1026,6 @@ public class LauncherBackupHelper implements BackupHelper {
Point spans = info.getMinSpans(mIdp, mContext);
widget.minSpanX = spans.x;
widget.minSpanY = spans.y;
-
return widget;
}
@@ -1131,9 +1130,8 @@ public class LauncherBackupHelper implements BackupHelper {
* @param journal a Journal protocol buffer
*/
private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
- FileOutputStream outStream = null;
try {
- outStream = new FileOutputStream(newState.getFileDescriptor());
+ FileOutputStream outStream = new FileOutputStream(newState.getFileDescriptor());
final byte[] journalBytes = writeCheckedBytes(journal);
outStream.write(journalBytes);
if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index e34bd57fdd..772fbf358f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -77,12 +77,14 @@ public interface LauncherCallbacks {
public boolean providesSearch();
public boolean startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, Rect sourceBounds);
+ @Deprecated
public boolean startSearchFromAllApps(String query);
@Deprecated
public void startVoice();
public boolean hasCustomContentToLeft();
public void populateCustomContentContainer();
public View getQsbBar();
+ public Bundle getAdditionalSearchWidgetOptions();
/*
* Extensions points for adding / replacing some other aspects of the Launcher experience.
@@ -99,6 +101,9 @@ public interface LauncherCallbacks {
public boolean isLauncherPreinstalled();
public AllAppsSearchBarController getAllAppsSearchBarController();
public List getPredictedApps();
+ public static final int SEARCH_BAR_HEIGHT_NORMAL = 0, SEARCH_BAR_HEIGHT_TALL = 1;
+ /** Must return one of {@link #SEARCH_BAR_HEIGHT_NORMAL} or {@link #SEARCH_BAR_HEIGHT_TALL} */
+ public int getSearchBarHeight();
/**
* Returning true will immediately result in a call to {@link #setLauncherOverlayView(ViewGroup,
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 18fe8ef868..5c1e83036f 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -73,9 +73,7 @@ class LauncherClings implements OnClickListener {
LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
| LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
// Set the flag to skip the folder cling
- String spKey = LauncherAppState.getSharedPreferencesKey();
- SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sp.edit();
+ SharedPreferences.Editor editor = Utilities.getPrefs(mLauncher).edit();
editor.putBoolean(Launcher.USER_HAS_MIGRATED, true);
editor.apply();
// Disable the migration cling
@@ -253,11 +251,9 @@ class LauncherClings implements OnClickListener {
!sharedPrefs.getBoolean(MIGRATION_CLING_DISMISSED_KEY, false);
}
- public static void synchonouslyMarkFirstRunClingDismissed(Context ctx) {
- SharedPreferences prefs = ctx.getSharedPreferences(
- LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true);
- editor.commit();
+ public static void markFirstRunClingDismissed(Context ctx) {
+ Utilities.getPrefs(ctx).edit()
+ .putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true)
+ .apply();
}
}
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index c08cd0bf52..6ce2293829 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -34,7 +34,7 @@ public class LauncherFiles {
WALLPAPER_CROP_PREFERENCES_KEY + XML,
WALLPAPER_IMAGES_DB,
WIDGET_PREVIEWS_DB,
- MANAGED_USER_PREFERENCES_KEY,
+ MANAGED_USER_PREFERENCES_KEY + XML,
APP_ICONS_DB));
// TODO: Delete these files on upgrade
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b5922c6a38..c8860e3ed0 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import android.app.SearchManager;
-import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -34,14 +33,12 @@ import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
-import android.os.TransactionTooLargeException;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
@@ -55,12 +52,15 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.model.MigrateFromRestoreTask;
+import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.CursorIconInfo;
+import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.StringFilter;
import com.android.launcher3.util.Thunk;
import java.lang.ref.WeakReference;
@@ -68,7 +68,6 @@ import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -98,7 +97,6 @@ public class LauncherModel extends BroadcastReceiver
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
private static final long INVALID_SCREEN_ID = -1L;
- @Thunk final boolean mAppsCanBeOnRemoveableStorage;
private final boolean mOldContentProviderExists;
@Thunk final LauncherAppState mApp;
@@ -137,9 +135,9 @@ public class LauncherModel extends BroadcastReceiver
@Thunk WeakReference mCallbacks;
// < only access in worker thread >
- AllAppsList mBgAllAppsList;
+ private final AllAppsList mBgAllAppsList;
// Entire list of widgets.
- WidgetsModel mBgWidgetsModel;
+ private final WidgetsModel mBgWidgetsModel;
// 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
@@ -166,9 +164,6 @@ public class LauncherModel extends BroadcastReceiver
// sBgWorkspaceScreens is the ordered set of workspace screens.
static final ArrayList sBgWorkspaceScreens = new ArrayList();
- // sBgWidgetProviders is the set of widget providers including custom internal widgets
- public static HashMap sBgWidgetProviders;
-
// sPendingPackages is a set of packages which could be on sdcard and are not available yet
static final HashMap> sPendingPackages =
new HashMap>();
@@ -201,9 +196,12 @@ public class LauncherModel extends BroadcastReceiver
ArrayList removed, UserHandleCompat user);
public void bindWidgetsRestored(ArrayList widgets);
public void bindRestoreItemsChange(HashSet updates);
- public void bindComponentsRemoved(ArrayList packageNames,
- ArrayList appInfos, UserHandleCompat user, int reason);
- public void bindAllPackages(WidgetsModel model);
+ public void bindWorkspaceComponentsRemoved(
+ HashSet packageNames, HashSet components,
+ UserHandleCompat user);
+ public void bindAppInfosRemoved(ArrayList appInfos);
+ public void notifyWidgetProvidersChanged();
+ public void bindWidgetsModel(WidgetsModel model);
public void bindSearchProviderChanged();
public boolean isAllAppsButtonRank(int rank);
public void onPageBoundSynchronously(int page);
@@ -217,7 +215,6 @@ public class LauncherModel extends BroadcastReceiver
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
Context context = app.getContext();
- mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
String oldProvider = context.getString(R.string.old_launcher_provider_uri);
// This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
// resource string.
@@ -1068,8 +1065,6 @@ public class LauncherModel extends BroadcastReceiver
/**
* Removes the specified item from the database
- * @param context
- * @param item
*/
public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
ArrayList items = new ArrayList();
@@ -1079,8 +1074,6 @@ public class LauncherModel extends BroadcastReceiver
/**
* Removes the specified items from the database
- * @param context
- * @param item
*/
static void deleteItemsFromDatabase(Context context, final ArrayList extends ItemInfo> items) {
final ContentResolver cr = context.getContentResolver();
@@ -1171,9 +1164,9 @@ public class LauncherModel extends BroadcastReceiver
}
/**
- * Remove the contents of the specified folder from the database
+ * Remove the specified folder and all its contents from the database.
*/
- public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
+ public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
final ContentResolver cr = context.getContentResolver();
Runnable r = new Runnable() {
@@ -1235,20 +1228,8 @@ public class LauncherModel extends BroadcastReceiver
@Override
public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
boolean replacing) {
- if (!replacing) {
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
- user));
- if (mAppsCanBeOnRemoveableStorage) {
- // Only rebind if we support removable storage. It catches the
- // case where
- // apps on the external sd card need to be reloaded
- startLoaderFromBackground();
- }
- } else {
- // If we are replacing then just update the packages in the list
- enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
- packageNames, user));
- }
+ enqueuePackageUpdated(
+ new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
}
@Override
@@ -1261,6 +1242,20 @@ public class LauncherModel extends BroadcastReceiver
}
}
+ @Override
+ public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
+ enqueuePackageUpdated(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_SUSPEND, packageNames,
+ user));
+ }
+
+ @Override
+ public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
+ enqueuePackageUpdated(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNSUSPEND, packageNames,
+ user));
+ }
+
/**
* Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
* ACTION_PACKAGE_CHANGED.
@@ -1282,6 +1277,14 @@ public class LauncherModel extends BroadcastReceiver
|| LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
UserManagerCompat.getInstance(context).enableAndResetCache();
forceReload();
+ } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
+ LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
+ UserHandleCompat user = UserHandleCompat.fromIntent(intent);
+ if (user != null) {
+ enqueuePackageUpdated(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
+ new String[0], user));
+ }
}
}
@@ -1733,23 +1736,10 @@ public class LauncherModel extends BroadcastReceiver
int countX = profile.numColumns;
int countY = profile.numRows;
- if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
- long migrationStartTime = System.currentTimeMillis();
- Log.v(TAG, "Starting workspace migration after restore");
- try {
- MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
- // Clear the flags before starting the task, so that we do not run the task
- // again, in case there was an uncaught error.
- MigrateFromRestoreTask.clearFlags(mContext);
- task.execute();
- } catch (Exception e) {
- Log.e(TAG, "Error during grid migration", e);
-
- // Clear workspace.
- mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
- }
- Log.v(TAG, "Workspace migration completed in "
- + (System.currentTimeMillis() - migrationStartTime));
+ if (GridSizeMigrationTask.ENABLED &&
+ !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
+ // Migration failed. Clear workspace.
+ mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
}
if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
@@ -1773,8 +1763,8 @@ public class LauncherModel extends BroadcastReceiver
.getInstance(mContext).updateAndGetActiveSessionCache();
sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
- final ArrayList itemsToRemove = new ArrayList();
- final ArrayList restoredRows = new ArrayList();
+ final ArrayList itemsToRemove = new ArrayList<>();
+ final ArrayList restoredRows = new ArrayList<>();
final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
@@ -1783,6 +1773,7 @@ public class LauncherModel extends BroadcastReceiver
// Load workspace in reverse order to ensure that latest items are loaded first (and
// before any earlier duplicates)
final LongArrayMap occupied = new LongArrayMap<>();
+ HashMap widgetProvidersMap = null;
try {
final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
@@ -1819,8 +1810,11 @@ public class LauncherModel extends BroadcastReceiver
final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
final LongSparseArray allUsers = new LongSparseArray<>();
+ final LongSparseArray quietMode = new LongSparseArray<>();
for (UserHandleCompat user : mUserManager.getUserProfiles()) {
- allUsers.put(mUserManager.getSerialNumberForUser(user), user);
+ long serialNo = mUserManager.getSerialNumberForUser(user);
+ allUsers.put(serialNo, user);
+ quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
}
ShortcutInfo info;
@@ -1831,6 +1825,7 @@ public class LauncherModel extends BroadcastReceiver
long serialNumber;
Intent intent;
UserHandleCompat user;
+ String targetPackage;
while (!mStopped && c.moveToNext()) {
try {
@@ -1849,6 +1844,7 @@ public class LauncherModel extends BroadcastReceiver
int promiseType = c.getInt(restoredIndex);
int disabledState = 0;
boolean itemReplaced = false;
+ targetPackage = null;
if (user == null) {
// User has been deleted remove the item.
itemsToRemove.add(id);
@@ -1862,6 +1858,9 @@ public class LauncherModel extends BroadcastReceiver
cn.getPackageName(), user);
boolean validComponent = validPkg &&
launcherApps.isActivityEnabledForProfile(cn, user);
+ if (validPkg) {
+ targetPackage = cn.getPackageName();
+ }
if (validComponent) {
if (restored) {
@@ -1869,6 +1868,9 @@ public class LauncherModel extends BroadcastReceiver
restoredRows.add(id);
restored = false;
}
+ if (quietMode.get(serialNumber)) {
+ disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
+ }
} else if (validPkg) {
intent = null;
if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
@@ -1937,9 +1939,8 @@ public class LauncherModel extends BroadcastReceiver
itemsToRemove.add(id);
continue;
}
- } else if (launcherApps.isAppEnabled(
- manager, cn.getPackageName(),
- PackageManager.GET_UNINSTALLED_PACKAGES)) {
+ } else if (PackageManagerHelper.isAppOnSdcard(
+ manager, cn.getPackageName())) {
// Package is present but not available.
allowMissingTarget = true;
disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
@@ -1982,7 +1983,7 @@ public class LauncherModel extends BroadcastReceiver
if (itemReplaced) {
if (user.equals(UserHandleCompat.myUserHandle())) {
- info = getAppShortcutInfo(manager, intent, user, context, null,
+ info = getAppShortcutInfo(intent, user, context, null,
cursorIconInfo.iconIndex, titleIndex,
false, useLowResIcon);
} else {
@@ -2005,12 +2006,17 @@ public class LauncherModel extends BroadcastReceiver
}
} else if (itemType ==
LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- info = getAppShortcutInfo(manager, intent, user, context, c,
+ info = getAppShortcutInfo(intent, user, context, c,
cursorIconInfo.iconIndex, titleIndex,
allowMissingTarget, useLowResIcon);
} else {
info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
+ // Shortcuts are only available on the primary profile
+ if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
+ disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+ }
+
// App shortcuts that used to be automatically added to Launcher
// didn't always have the correct intent flags set, so do that
// here
@@ -2038,7 +2044,7 @@ public class LauncherModel extends BroadcastReceiver
if (info.promisedIntent != null) {
info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
}
- info.isDisabled = disabledState;
+ info.isDisabled |= disabledState;
if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
}
@@ -2141,10 +2147,14 @@ public class LauncherModel extends BroadcastReceiver
final boolean wasProviderReady = (restoreStatus &
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
- final LauncherAppWidgetProviderInfo provider =
- LauncherModel.getProviderInfo(context,
+ if (widgetProvidersMap == null) {
+ widgetProvidersMap = AppWidgetManagerCompat
+ .getInstance(mContext).getAllProvidersMap();
+ }
+ final AppWidgetProviderInfo provider = widgetProvidersMap.get(
+ new ComponentKey(
ComponentName.unflattenFromString(savedProvider),
- user);
+ user));
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget &&
@@ -2387,7 +2397,7 @@ public class LauncherModel extends BroadcastReceiver
Collections.sort(allWorkspaceItems, new Comparator() {
@Override
public int compare(ItemInfo lhs, ItemInfo rhs) {
- return (int) (lhs.container - rhs.container);
+ return Utilities.longCompare(lhs.container, rhs.container);
}
});
for (ItemInfo info : allWorkspaceItems) {
@@ -2469,7 +2479,7 @@ public class LauncherModel extends BroadcastReceiver
lhs.cellY * cellCountX + lhs.cellX);
long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
rhs.cellY * cellCountX + rhs.cellX);
- return (int) (lr - rr);
+ return Utilities.longCompare(lr, rr);
}
});
}
@@ -2757,18 +2767,16 @@ public class LauncherModel extends BroadcastReceiver
@SuppressWarnings("unchecked")
final ArrayList list
= (ArrayList) mBgAllAppsList.data.clone();
- final WidgetsModel widgetList = mBgWidgetsModel.clone();
Runnable r = new Runnable() {
public void run() {
final long t = SystemClock.uptimeMillis();
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAllApplications(list);
- callbacks.bindAllPackages(widgetList);
}
if (DEBUG_LOADERS) {
Log.d(TAG, "bound all " + list.size() + " apps from cache in "
- + (SystemClock.uptimeMillis()-t) + "ms");
+ + (SystemClock.uptimeMillis() - t) + "ms");
}
}
};
@@ -2808,12 +2816,12 @@ public class LauncherModel extends BroadcastReceiver
if (apps == null || apps.isEmpty()) {
return;
}
-
+ boolean quietMode = mUserManager.isQuietModeEnabled(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i);
// This builds the icon bitmaps.
- mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
+ mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
}
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
@@ -2856,7 +2864,7 @@ public class LauncherModel extends BroadcastReceiver
callbacks.bindAllApplications(added);
if (DEBUG_LOADERS) {
Log.d(TAG, "bound " + added.size() + " apps in "
- + (SystemClock.uptimeMillis() - bindTime) + "ms");
+ + (SystemClock.uptimeMillis() - bindTime) + "ms");
}
} else {
Log.i(TAG, "not binding apps: no Launcher activity");
@@ -2865,8 +2873,6 @@ public class LauncherModel extends BroadcastReceiver
});
// Cleanup any data stored for a deleted user.
ManagedProfileHeuristic.processAllUsers(profiles, mContext);
-
- loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
@@ -2933,9 +2939,6 @@ public class LauncherModel extends BroadcastReceiver
}
});
}
-
- // Reload widget list. No need to refresh, as we only want to update the icons and labels.
- loadAndBindWidgetsAndShortcuts(callbacks, false);
}
void enqueuePackageUpdated(PackageUpdatedTask task) {
@@ -2958,10 +2961,7 @@ public class LauncherModel extends BroadcastReceiver
packagesUnavailable.clear();
for (String pkg : entry.getValue()) {
if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
- boolean packageOnSdcard = launcherApps.isAppEnabled(
- manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
- if (packageOnSdcard) {
- Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
+ if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
packagesUnavailable.add(pkg);
} else {
Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
@@ -2993,7 +2993,9 @@ public class LauncherModel extends BroadcastReceiver
public static final int OP_UPDATE = 2;
public static final int OP_REMOVE = 3; // uninstlled
public static final int OP_UNAVAILABLE = 4; // external media unmounted
-
+ public static final int OP_SUSPEND = 5; // package suspended
+ public static final int OP_UNSUSPEND = 6; // package unsuspended
+ public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
mOp = op;
@@ -3010,6 +3012,8 @@ 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)));
switch (mOp) {
case OP_ADD: {
for (int i=0; i removedApps = new ArrayList();
if (mBgAllAppsList.added.size() > 0) {
- added = new ArrayList(mBgAllAppsList.added);
+ added = new ArrayList<>(mBgAllAppsList.added);
mBgAllAppsList.added.clear();
}
if (mBgAllAppsList.modified.size() > 0) {
- modified = new ArrayList(mBgAllAppsList.modified);
+ modified = new ArrayList<>(mBgAllAppsList.modified);
mBgAllAppsList.modified.clear();
}
if (mBgAllAppsList.removed.size() > 0) {
@@ -3069,14 +3092,7 @@ public class LauncherModel extends BroadcastReceiver
mBgAllAppsList.removed.clear();
}
- final Callbacks callbacks = getCallback();
- if (callbacks == null) {
- Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
- return;
- }
-
- final HashMap addedOrUpdatedApps =
- new HashMap();
+ final HashMap addedOrUpdatedApps = new HashMap<>();
if (added != null) {
addAppsToAllApps(context, added);
@@ -3086,6 +3102,7 @@ public class LauncherModel extends BroadcastReceiver
}
if (modified != null) {
+ final Callbacks callbacks = getCallback();
final ArrayList modifiedFinal = modified;
for (AppInfo ai : modified) {
addedOrUpdatedApps.put(ai.componentName, ai);
@@ -3102,12 +3119,11 @@ public class LauncherModel extends BroadcastReceiver
}
// Update shortcut infos
- if (mOp == OP_ADD || mOp == OP_UPDATE) {
+ if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
final ArrayList updatedShortcuts = new ArrayList();
final ArrayList removedShortcuts = new ArrayList();
final ArrayList widgets = new ArrayList();
- HashSet packageSet = new HashSet(Arrays.asList(packages));
synchronized (sBgLock) {
for (ItemInfo info : sBgItemsIdMap) {
if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
@@ -3117,7 +3133,7 @@ public class LauncherModel extends BroadcastReceiver
// Update shortcuts which use iconResource.
if ((si.iconResource != null)
- && packageSet.contains(si.iconResource.packageName)) {
+ && pkgFilter.matches(si.iconResource.packageName)) {
Bitmap icon = Utilities.createIconBitmap(
si.iconResource.packageName,
si.iconResource.resourceName, context);
@@ -3129,7 +3145,7 @@ public class LauncherModel extends BroadcastReceiver
}
ComponentName cn = si.getTargetComponent();
- if (cn != null && packageSet.contains(cn.getPackageName())) {
+ if (cn != null && pkgFilter.matches(cn.getPackageName())) {
AppInfo appInfo = addedOrUpdatedApps.get(cn);
if (si.isPromise()) {
@@ -3177,9 +3193,9 @@ public class LauncherModel extends BroadcastReceiver
infoUpdated = true;
}
- if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
- // Since package was just updated, the target must be available now.
- si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
+ int oldDisabledFlags = si.isDisabled;
+ si.isDisabled = flagOp.apply(si.isDisabled);
+ if (si.isDisabled != oldDisabledFlags) {
shortcutUpdated = true;
}
}
@@ -3190,11 +3206,11 @@ public class LauncherModel extends BroadcastReceiver
if (infoUpdated) {
updateItemInDatabase(context, si);
}
- } else if (info instanceof LauncherAppWidgetInfo) {
+ } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
if (mUser.equals(widgetInfo.user)
&& widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
- && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+ && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
widgetInfo.restoreStatus &=
~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
@@ -3212,6 +3228,7 @@ public class LauncherModel extends BroadcastReceiver
}
if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
+ final Callbacks callbacks = getCallback();
mHandler.post(new Runnable() {
public void run() {
@@ -3227,6 +3244,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
if (!widgets.isEmpty()) {
+ final Callbacks callbacks = getCallback();
mHandler.post(new Runnable() {
public void run() {
Callbacks cb = getCallback();
@@ -3238,171 +3256,104 @@ public class LauncherModel extends BroadcastReceiver
}
}
- final ArrayList removedPackageNames =
- new ArrayList();
- if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
+ final HashSet removedPackages = new HashSet<>();
+ final HashSet removedComponents = new HashSet<>();
+ if (mOp == OP_REMOVE) {
// Mark all packages in the broadcast to be removed
- removedPackageNames.addAll(Arrays.asList(packages));
+ Collections.addAll(removedPackages, packages);
+
+ // No need to update the removedComponents as
+ // removedPackages is a super-set of removedComponents
} else if (mOp == OP_UPDATE) {
// Mark disabled packages in the broadcast to be removed
for (int i=0; i infos = getItemInfoForComponentName(a.componentName, mUser);
- deleteItemsFromDatabase(context, infos);
- }
- removeReason = 0;
+ if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
+ for (String pn : removedPackages) {
+ deletePackageFromDatabase(context, pn, mUser);
+ }
+ for (ComponentName cn : removedComponents) {
+ deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
}
// Remove any queued items from the install queue
- InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
+ InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+
// Call the components-removed callback
+ final Callbacks callbacks = getCallback();
mHandler.post(new Runnable() {
public void run() {
Callbacks cb = getCallback();
if (callbacks == cb && cb != null) {
- callbacks.bindComponentsRemoved(
- removedPackageNames, removedApps, mUser, removeReason);
+ callbacks.bindWorkspaceComponentsRemoved(
+ removedPackages, removedComponents, mUser);
}
}
});
}
- // Update widgets
- if (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE) {
- // Always refresh for a package event on secondary user
- boolean needToRefresh = !mUser.equals(UserHandleCompat.myUserHandle());
-
- // Refresh widget list, if the package already had a widget.
- synchronized (sBgLock) {
- if (sBgWidgetProviders != null) {
- HashSet pkgSet = new HashSet<>();
- Collections.addAll(pkgSet, mPackages);
-
- for (ComponentKey key : sBgWidgetProviders.keySet()) {
- needToRefresh |= key.user.equals(mUser) &&
- pkgSet.contains(key.componentName.getPackageName());
+ if (!removedApps.isEmpty()) {
+ // Remove corresponding apps from All-Apps
+ final Callbacks callbacks = getCallback();
+ mHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = getCallback();
+ if (callbacks == cb && cb != null) {
+ callbacks.bindAppInfosRemoved(removedApps);
}
}
- }
-
- if (!needToRefresh && mOp != OP_REMOVE) {
- // Refresh widget list, if there is any newly added widget
- PackageManager pm = context.getPackageManager();
- for (String pkg : mPackages) {
- needToRefresh |= !pm.queryBroadcastReceivers(
- new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
- .setPackage(pkg), 0).isEmpty();
- }
- }
-
- loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh);
+ });
}
- // Write all the logs to disk
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.dumpLogsToLocalData();
+ // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
+ // get widget update signals.
+ if (!Utilities.ATLEAST_MARSHMALLOW &&
+ (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
+ final Callbacks callbacks = getCallback();
+ mHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = getCallback();
+ if (callbacks == cb && cb != null) {
+ callbacks.notifyWidgetProvidersChanged();
+ }
}
- }
- });
- }
- }
-
- public static List getWidgetProviders(Context context,
- boolean refresh) {
- ArrayList results =
- new ArrayList();
- try {
- synchronized (sBgLock) {
- if (sBgWidgetProviders == null || refresh) {
- HashMap tmpWidgetProviders
- = new HashMap<>();
- AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
- LauncherAppWidgetProviderInfo info;
-
- List widgets = wm.getAllProviders();
- for (AppWidgetProviderInfo pInfo : widgets) {
- info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
- UserHandleCompat user = wm.getUser(info);
- tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
- }
-
- Collection customWidgets = Launcher.getCustomAppWidgets().values();
- for (CustomAppWidget widget : customWidgets) {
- info = new LauncherAppWidgetProviderInfo(context, widget);
- UserHandleCompat user = wm.getUser(info);
- tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
- }
- // Replace the global list at the very end, so that if there is an exception,
- // previously loaded provider list is used.
- sBgWidgetProviders = tmpWidgetProviders;
- }
- results.addAll(sBgWidgetProviders.values());
- return results;
- }
- } catch (Exception e) {
- if (e.getCause() instanceof TransactionTooLargeException) {
- // the returned value may be incomplete and will not be refreshed until the next
- // time Launcher starts.
- // TODO: after figuring out a repro step, introduce a dirty bit to check when
- // onResume is called to refresh the widget provider list.
- synchronized (sBgLock) {
- if (sBgWidgetProviders != null) {
- results.addAll(sBgWidgetProviders.values());
- }
- return results;
- }
- } else {
- throw e;
+ });
}
}
}
- public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
- UserHandleCompat user) {
- synchronized (sBgLock) {
- if (sBgWidgetProviders == null) {
- getWidgetProviders(ctx, false /* refresh */);
+ private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Callbacks cb = getCallback();
+ if (callbacks == cb && cb != null) {
+ callbacks.bindWidgetsModel(model);
+ }
}
- return sBgWidgetProviders.get(new ComponentKey(name, user));
- }
+ });
}
- public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
-
+ public void refreshAndBindWidgetsAndShortcuts(
+ final Callbacks callbacks, final boolean bindFirst) {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
- updateWidgetsModel(refresh);
- final WidgetsModel model = mBgWidgetsModel.clone();
-
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Callbacks cb = getCallback();
- if (callbacks == cb && cb != null) {
- callbacks.bindAllPackages(model);
- }
- }
- });
+ if (bindFirst && !mBgWidgetsModel.isEmpty()) {
+ bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
+ }
+ final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
+ bindWidgetsModel(callbacks, model);
// update the Widget entries inside DB on the worker thread.
LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
model.getRawList());
@@ -3410,20 +3361,6 @@ public class LauncherModel extends BroadcastReceiver
});
}
- /**
- * Returns a list of ResolveInfos/AppWidgetInfos.
- *
- * @see #loadAndBindWidgetsAndShortcuts
- */
- @Thunk void updateWidgetsModel(boolean refresh) {
- PackageManager packageManager = mApp.getContext().getPackageManager();
- final ArrayList