diff --git a/Android.mk b/Android.mk index 61ca3a3b39..b95e3d8114 100644 --- a/Android.mk +++ b/Android.mk @@ -17,7 +17,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := user eng development +LOCAL_MODULE_TAGS := user LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 966848321b..272f7c12c4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -20,7 +20,8 @@ + android:sharedUserId="android.uid.shared" + android:sharedUserLabel="@string/application_name"> + android:theme="@style/Theme" + android:windowSoftInputMode="stateUnspecified|adjustPan"> diff --git a/res/drawable/bg_appwidget_error.9.png b/res/drawable/bg_appwidget_error.9.png new file mode 100644 index 0000000000..507742471f Binary files /dev/null and b/res/drawable/bg_appwidget_error.9.png differ diff --git a/res/drawable/clock_dial.png b/res/drawable/clock_dial.png deleted file mode 100644 index eda3d17f7a..0000000000 Binary files a/res/drawable/clock_dial.png and /dev/null differ diff --git a/res/drawable/clock_hour.png b/res/drawable/clock_hour.png deleted file mode 100644 index fcfd948066..0000000000 Binary files a/res/drawable/clock_hour.png and /dev/null differ diff --git a/res/drawable/clock_minute.png b/res/drawable/clock_minute.png deleted file mode 100644 index afc0a3ff9c..0000000000 Binary files a/res/drawable/clock_minute.png and /dev/null differ diff --git a/res/drawable/ic_launcher_alarmclock.png b/res/drawable/ic_launcher_alarmclock.png deleted file mode 100755 index 30ff2671a2..0000000000 Binary files a/res/drawable/ic_launcher_alarmclock.png and /dev/null differ diff --git a/res/drawable/ic_launcher_application.png b/res/drawable/ic_launcher_application.png new file mode 100644 index 0000000000..75024841d3 Binary files /dev/null and b/res/drawable/ic_launcher_application.png differ diff --git a/res/drawable/ic_launcher_appwidget.png b/res/drawable/ic_launcher_appwidget.png new file mode 100644 index 0000000000..cb40a1988b Binary files /dev/null and b/res/drawable/ic_launcher_appwidget.png differ diff --git a/res/drawable/ic_launcher_empty.png b/res/drawable/ic_launcher_empty.png new file mode 100644 index 0000000000..59bb6c5060 Binary files /dev/null and b/res/drawable/ic_launcher_empty.png differ diff --git a/res/drawable/ic_search_gadget.png b/res/drawable/ic_search_widget.png similarity index 100% rename from res/drawable/ic_search_gadget.png rename to res/drawable/ic_search_widget.png diff --git a/res/drawable/picture_frame.9.png b/res/drawable/picture_frame.9.png deleted file mode 100644 index b153260d9e..0000000000 Binary files a/res/drawable/picture_frame.9.png and /dev/null differ diff --git a/res/drawable/texture_brushed_steel.png b/res/drawable/texture_brushed_steel.png new file mode 100644 index 0000000000..73b3dfe63e Binary files /dev/null and b/res/drawable/texture_brushed_steel.png differ diff --git a/res/drawable/wallpaper.jpg b/res/drawable/wallpaper.jpg deleted file mode 100644 index d19b642825..0000000000 Binary files a/res/drawable/wallpaper.jpg and /dev/null differ diff --git a/res/drawable/wallpaper_beach.jpg b/res/drawable/wallpaper_beach.jpg index c059cc3db9..b502092a7c 100644 Binary files a/res/drawable/wallpaper_beach.jpg and b/res/drawable/wallpaper_beach.jpg differ diff --git a/res/drawable/wallpaper_beach_small.jpg b/res/drawable/wallpaper_beach_small.jpg index 3508509357..46051cc4e7 100644 Binary files a/res/drawable/wallpaper_beach_small.jpg and b/res/drawable/wallpaper_beach_small.jpg differ diff --git a/res/drawable/wallpaper_blue.jpg b/res/drawable/wallpaper_blue.jpg index 032f598189..0edd6bda8b 100644 Binary files a/res/drawable/wallpaper_blue.jpg and b/res/drawable/wallpaper_blue.jpg differ diff --git a/res/drawable/wallpaper_blue_small.jpg b/res/drawable/wallpaper_blue_small.jpg index e987c69dc7..5cf585a0e1 100644 Binary files a/res/drawable/wallpaper_blue_small.jpg and b/res/drawable/wallpaper_blue_small.jpg differ diff --git a/res/drawable/wallpaper_green.jpg b/res/drawable/wallpaper_green.jpg index 89b308b148..36c9e08d75 100644 Binary files a/res/drawable/wallpaper_green.jpg and b/res/drawable/wallpaper_green.jpg differ diff --git a/res/drawable/wallpaper_green_small.jpg b/res/drawable/wallpaper_green_small.jpg index 16790d2a08..d7c28ec1e3 100644 Binary files a/res/drawable/wallpaper_green_small.jpg and b/res/drawable/wallpaper_green_small.jpg differ diff --git a/res/drawable/wallpaper_grey.jpg b/res/drawable/wallpaper_grey.jpg index 7898568cfd..ca48e6afc9 100644 Binary files a/res/drawable/wallpaper_grey.jpg and b/res/drawable/wallpaper_grey.jpg differ diff --git a/res/drawable/wallpaper_grey_small.jpg b/res/drawable/wallpaper_grey_small.jpg index 12ca3e22b7..1734d2e966 100644 Binary files a/res/drawable/wallpaper_grey_small.jpg and b/res/drawable/wallpaper_grey_small.jpg differ diff --git a/res/drawable/wallpaper_jellyfish_small.jpg b/res/drawable/wallpaper_jellyfish_small.jpg index e140597c86..4e5d134643 100644 Binary files a/res/drawable/wallpaper_jellyfish_small.jpg and b/res/drawable/wallpaper_jellyfish_small.jpg differ diff --git a/res/drawable/wallpaper_lake.jpg b/res/drawable/wallpaper_lake.jpg deleted file mode 100644 index 5ba522f57b..0000000000 Binary files a/res/drawable/wallpaper_lake.jpg and /dev/null differ diff --git a/res/drawable/wallpaper_lake_small.jpg b/res/drawable/wallpaper_lake_small.jpg index 179b26a057..36d56ccc7b 100644 Binary files a/res/drawable/wallpaper_lake_small.jpg and b/res/drawable/wallpaper_lake_small.jpg differ diff --git a/res/drawable/wallpaper_mountain_small.jpg b/res/drawable/wallpaper_mountain_small.jpg index 2416474882..e2d5ac2651 100644 Binary files a/res/drawable/wallpaper_mountain_small.jpg and b/res/drawable/wallpaper_mountain_small.jpg differ diff --git a/res/drawable/wallpaper_path.jpg b/res/drawable/wallpaper_path.jpg index f4cb02ce0c..e1c98c0569 100644 Binary files a/res/drawable/wallpaper_path.jpg and b/res/drawable/wallpaper_path.jpg differ diff --git a/res/drawable/wallpaper_path_small.jpg b/res/drawable/wallpaper_path_small.jpg index a1af4cdd86..f47d7a74f8 100644 Binary files a/res/drawable/wallpaper_path_small.jpg and b/res/drawable/wallpaper_path_small.jpg differ diff --git a/res/drawable/wallpaper_pink.jpg b/res/drawable/wallpaper_pink.jpg index 1b9f226e05..74403e5529 100644 Binary files a/res/drawable/wallpaper_pink.jpg and b/res/drawable/wallpaper_pink.jpg differ diff --git a/res/drawable/wallpaper_pink_small.jpg b/res/drawable/wallpaper_pink_small.jpg index 7b7ad587e6..42b17709fb 100644 Binary files a/res/drawable/wallpaper_pink_small.jpg and b/res/drawable/wallpaper_pink_small.jpg differ diff --git a/res/drawable/wallpaper_ripples.jpg b/res/drawable/wallpaper_ripples.jpg deleted file mode 100644 index 8624c860f3..0000000000 Binary files a/res/drawable/wallpaper_ripples.jpg and /dev/null differ diff --git a/res/drawable/wallpaper_ripples_small.jpg b/res/drawable/wallpaper_ripples_small.jpg deleted file mode 100644 index ce07d38af2..0000000000 Binary files a/res/drawable/wallpaper_ripples_small.jpg and /dev/null differ diff --git a/res/drawable/wallpaper_road.jpg b/res/drawable/wallpaper_road.jpg index 88611a4ec7..3aaa639545 100644 Binary files a/res/drawable/wallpaper_road.jpg and b/res/drawable/wallpaper_road.jpg differ diff --git a/res/drawable/wallpaper_snow_leopard.jpg b/res/drawable/wallpaper_snow_leopard.jpg index 0d33157a59..bcbc51e7b8 100644 Binary files a/res/drawable/wallpaper_snow_leopard.jpg and b/res/drawable/wallpaper_snow_leopard.jpg differ diff --git a/res/drawable/wallpaper_snow_leopard_small.jpg b/res/drawable/wallpaper_snow_leopard_small.jpg index ec976aad98..99db5c6931 100644 Binary files a/res/drawable/wallpaper_snow_leopard_small.jpg and b/res/drawable/wallpaper_snow_leopard_small.jpg differ diff --git a/res/drawable/wallpaper_sunrise_small.jpg b/res/drawable/wallpaper_sunrise_small.jpg index aadd980efe..248b7e78fe 100644 Binary files a/res/drawable/wallpaper_sunrise_small.jpg and b/res/drawable/wallpaper_sunrise_small.jpg differ diff --git a/res/drawable/wallpaper_sunset_small.jpg b/res/drawable/wallpaper_sunset_small.jpg index eff8f0da2e..d5e5add39c 100644 Binary files a/res/drawable/wallpaper_sunset_small.jpg and b/res/drawable/wallpaper_sunset_small.jpg differ diff --git a/res/drawable/wallpaper_zanzibar_small.jpg b/res/drawable/wallpaper_zanzibar_small.jpg index 7307a73985..700a7fee1e 100644 Binary files a/res/drawable/wallpaper_zanzibar_small.jpg and b/res/drawable/wallpaper_zanzibar_small.jpg differ diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 11a81fd8ee..2b262c37ee 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -16,7 +16,6 @@ - + android:bottomOffset="7dip" + android:handle="@+id/all_apps" + android:content="@+id/content"> - + - + android:topOffset="5dip" + android:bottomOffset="7dip" + android:handle="@+id/all_apps" + android:content="@+id/content"> - + - - + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:gravity="center_vertical" + android:drawablePadding="14dip" + android:paddingLeft="15dip" + android:paddingRight="15dip" /> diff --git a/res/layout/widget_photo_frame.xml b/res/layout/appwidget_error.xml similarity index 50% rename from res/layout/widget_photo_frame.xml rename to res/layout/appwidget_error.xml index 9fe3b80bce..03d4ae424c 100644 --- a/res/layout/widget_photo_frame.xml +++ b/res/layout/appwidget_error.xml @@ -1,5 +1,5 @@ - - + diff --git a/res/layout/create_shortcut_list.xml b/res/layout/create_shortcut_list.xml index 63ca2b8795..aa1673375b 100644 --- a/res/layout/create_shortcut_list.xml +++ b/res/layout/create_shortcut_list.xml @@ -17,11 +17,10 @@ ** limitations under the License. */ --> - diff --git a/res/layout/widget_search.xml b/res/layout/widget_search.xml index 27e591db99..209716d529 100644 --- a/res/layout/widget_search.xml +++ b/res/layout/widget_search.xml @@ -26,7 +26,7 @@ android:layout_height="wrap_content" android:src="@drawable/google_logo" /> - + + diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index c5afda2ad8..dea039f701 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -28,6 +28,8 @@ "Přidat na plochu" "Aplikace" "Zástupce" + "Hledat" + "Složka" "Složka Live" "Miniaplikace" "Tapeta" @@ -36,6 +38,8 @@ "Rámeček fotografie" "Vyhledávání" "Na této ploše již není místo." + "Vyberte zástupce" + "Vyberte složku Live" "Přidat" "Tapeta" "Hledat" @@ -50,4 +54,6 @@ "zápis nastavení a odkazů plochy" "Povoluje aplikaci změnit nastavení a odkazy plochy." "Vyhledávání Google" + + diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 3f0c42a619..c40585215a 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -28,6 +28,8 @@ "Zur Startseite hinzufügen" "Anwendung" "Verknüpfung" + "Suchen" + "Ordner" "Live-Ordner" "Widget" "Hintergrund" @@ -36,6 +38,8 @@ "Bildrahmen" "Suchen" "Auf der Startseite ist kein Platz mehr vorhanden." + "Tastenkürzel auswählen" + "Live-Ordner auswählen" "Hinzufügen" "Hintergrund" "Suchen" @@ -50,4 +54,6 @@ "Einstellungen und Shortcuts für Startseite schreiben" "Ermöglicht einer Anwendung, die Einstellungen und Shortcuts auf der Startseite zu ändern." "Google-Suche" + + diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 161616dcfb..20d2605be0 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -17,9 +17,9 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Página principal" "Carpeta" - "Seleccionar papel tapiz de" - "Establecer papel tapiz" - "Galería de papel tapiz" + "Seleccionar fondo de pantalla de" + "Establecer fondo de pantalla" + "Galería de fondo de pantalla" "La aplicación no está instalada en el teléfono." "Nombre de carpeta" "Cambiar nombre de carpeta" @@ -28,19 +28,23 @@ "Añadir a pantalla de página principal" "Aplicación" "Acceso directo" + "Búsqueda" + "Carpeta" "Carpeta activa" "Widget" - "Papel tapiz" + "Fondo de pantalla" "Carpeta" "Reloj" "Picture frame" "Búsqueda de Google" "No queda espacio en esta pantalla de página principal." + "Seleccionar acceso directo" + "Seleccionar carpeta activa" "Añadir" - "Papel tapiz" - "Búsqueda de Google" + "Fondo de pantalla" + "Buscar con Google" "Notificaciones" - "Configuración" + "Ajustes" "instalar accesos directos" "Permite que una aplicación añada accesos directos sin intervención del usuario." "desinstalar accesos directos" @@ -50,4 +54,6 @@ "escribir información de accesos directos y de configuración de la página principal" "Permite que una aplicación modifique la configuración y los accesos directos de la página principal." "Búsqueda de Google" + + diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index effdc7aff7..c01ef47d2f 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -17,9 +17,9 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> "Accueil" "Dossier" - "Sélectionner l\'arrière-plan à partir de" - "Configurer l\'arrière-plan" - "Galerie des arrière-plans" + "Sélectionner à partir de..." + "Sélectionner" + "Galerie" "L\'application n\'est pas installée sur votre téléphone." "Nom du dossier" "Renommer le dossier" @@ -28,14 +28,18 @@ "Ajouter à l\'écran d\'accueil" "Application" "Raccourci" - "Dossier live" + "Recherche" + "Dossier" + "Dossier actif" "Widget" "Arrière-plan" "Dossier" "Horloge" "Cadre d\'image" "Rechercher" - "Plus d\'espace libre sur l\'écran Accueil." + "Plus d\'espace libre sur l\'écran d\'accueil." + "Sélectionner un raccourci" + "Sélectionner dossier actif" "Ajouter" "Arrière-plan" "Rechercher" @@ -45,9 +49,11 @@ "Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur." "désinstaller les raccourcis" "Permet à une application de supprimer les raccourcis sans l\'intervention de l\'utilisateur." - "lire les paramètres et les raccourcis de la page d\'accueil" + "Lire les paramètres et les raccourcis de la page d\'accueil" "Permet à une application de lire les paramètres et raccourcis de la page d\'accueil." - "écrire les paramètres de la page d\'accueil et des raccourcis" + "Enregistrer les paramètres de la page d\'accueil et des raccourcis" "Permet à une application de modifier les paramètres et les raccourcis de la page d\'accueil." "Recherche Google" + + diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index d38fa30c35..47c6f9f798 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -27,7 +27,9 @@ "Annulla" "Aggiungi a schermata Home" "Applicazione" - "Collegamento" + "Scorciatoia" + "Ricerca" + "Cartella" "Cartella dinamica" "Widget" "Sfondo" @@ -36,18 +38,22 @@ "Cornice immagini" "Ricerca" "Spazio nella schermata Home esaurito." + "Seleziona collegamento" + "Seleziona cartella dinamica" "Aggiungi" "Sfondo" "Cerca" "Notifiche" "Impostazioni" - "aggiungere collegamenti" - "Consente a un\'applicazione di aggiungere collegamenti automaticamente." - "eliminare collegamenti" - "Consente a un\'applicazione di rimuovere collegamenti automaticamente." - "leggere impostazioni e collegamenti in Home" - "Consente a un\'applicazione di leggere le impostazioni e i collegamenti in Home." - "creare impostazioni e collegamenti in Home" - "Consente a un\'applicazione di modificare le impostazioni e i collegamenti in Home." + "aggiungere scorciatorie" + "Consente a un\'applicazione di aggiungere scorciatoie automaticamente." + "eliminare scorciatoie" + "Consente a un\'applicazione di rimuovere scorciatoie automaticamente." + "leggere impostazioni e scorciatoie in Home" + "Consente a un\'applicazione di leggere le impostazioni e le scorciatoie in Home." + "creare impostazioni e scorciatoie in Home" + "Consente a un\'applicazione di modificare le impostazioni e le scorciatoie in Home." "Ricerca Google" + + diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index c3b1ff8c4b..cf71c65a9d 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -28,6 +28,8 @@ "ホーム画面に追加" "アプリケーション" "ショートカット" + "検索" + "フォルダ" "ライブフォルダ" "ウィジェット" "壁紙" @@ -35,19 +37,23 @@ "時計" "写真フレーム" "検索" - "このホーム画面には空きスペースがありません。" + "ホーム画面に空きスペースがありません。" + "ショートカットを選択" + "ライブフォルダを選択" "追加" "壁紙" "検索" "通知" "設定" "ショートカットのインストール" - "ユーザー操作なしで、ショートカットをアプリケーションで追加できるようにします。" + "ユーザー操作なしでショートカットの追加をアプリケーションに許可します。" "ショートカットのアンインストール" - "ユーザー操作なしで、ショートカットをアプリケーションで削除できるようにします。" - "ホームの設定とショートカットの読み取り" + "ユーザー操作なしでショートカットの削除をアプリケーションに許可します。" + "ホーム設定とショートカットの読み取り" "ホームの設定とショートカットの読み取りをアプリケーションに許可します。" "ホームの設定とショートカットの書き込み" "ホームの設定とショートカットの変更をアプリケーションに許可します。" "Google検索" + + diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml new file mode 100644 index 0000000000..75ad6d8a69 --- /dev/null +++ b/res/values-ko/strings.xml @@ -0,0 +1,59 @@ + + + + "홈" + "폴더" + "배경화면 선택" + "배경화면 설정" + "배경화면 갤러리" + "전화기에 설치되어 있지 않은 응용프로그램입니다." + "폴더 이름" + "폴더 이름 바꾸기" + "확인" + "취소" + "홈 화면에 추가" + "응용프로그램" + "바로가기" + "검색" + "폴더" + "라이브 폴더" + "위젯" + "배경화면" + "폴더" + "시계" + "사진 프레임" + "검색" + "홈 화면에 더 이상 공간이 없습니다." + "바로가기 선택" + "라이브 폴더 선택" + "추가" + "배경화면" + "검색" + "알림" + "설정" + "바로가기 설치" + "응용프로그램이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다." + "바로가기 제거" + "응용프로그램이 사용자의 작업 없이 바로가기를 제거할 수 있도록 합니다." + "홈 설정 및 바로가기 읽기" + "응용프로그램이 홈에 있는 설정 및 바로가기를 읽을 수 있습니다." + "홈 설정 및 바로가기 쓰기" + "응용프로그램이 홈에 있는 설정 및 바로가기를 변경할 수 있습니다." + "Google 검색" + + + diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml new file mode 100644 index 0000000000..b14a5da7b7 --- /dev/null +++ b/res/values-nb/strings.xml @@ -0,0 +1,59 @@ + + + + "Hjem" + "Mappe" + "Velg bakgrunnsbilde fra" + "Velg bakgrunnsbilde" + "Bildegalleri" + "Applikasjonen er ikke installert." + "Mappenavn" + "Gi nytt navn til mappe" + "OK" + "Avbryt" + "Legg til skrivebord" + "Applikasjon" + "Snarvei" + "Søk" + "Mappe" + "Aktiv mappe" + "Skrivebordselement" + "Bakgrunnsbilde" + "Mappe" + "Klokke" + "Bilderamme" + "Søk" + "Ikke nok plass på skrivebordet." + "Velg snarvei" + "Velg aktiv mappe" + "Legg til" + "Bakgrunnsbilde" + "Søk" + "Varslinger" + "Innstillinger" + "installere snarveier" + "Lar applikasjonen legge til snarveier uten å involvere brukeren." + "avinstallere snarveier" + "Lar applikasjonen fjerne snarveier uten å involvere brukeren." + "lese skrivebordsinnstillinger og -snarveier" + "Lar applikasjonen lese innstillinger og snarveier fra skrivebordet." + "skrive skrivebordsinnstillinger og -snarveier" + "Lar applikasjonen endre innstillinger og snarveier på skrivebordet." + "Google-søk" + + + diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 9e55d8f44f..e4959c0db7 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -28,6 +28,8 @@ "Toevoegen aan startpagina" "Toepassing" "Snelkoppeling" + "Zoeken" + "Map" "Live map" "Widget" "Achtergrond" @@ -36,6 +38,8 @@ "Fotolijstje" "Zoeken" "Er is geen ruimte meer op dit startscherm." + "Snelkoppeling selecteren" + "Live map selecteren" "Toevoegen" "Achtergrond" "Zoeken" @@ -49,5 +53,7 @@ "Hiermee kan een toepassing de instellingen en snelkoppelingen op de startpagina lezen." "instellingen en snelkoppelingen voor de startpagina schrijven" "Hiermee kan een toepassing de instellingen en snelkoppelingen op de startpagina wijzigen." - "Zoeken met Google" + "Google Zoeken" + + diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 935695434e..99718cb160 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -15,7 +15,7 @@ --> - "Strona główna" + "Sieć" "Folder" "Wybierz tapetę" "Ustaw tapetę" @@ -28,14 +28,18 @@ "Dodaj do strony głównej" "Aplikacja" "Skrót" - "Folder Live" + "Wyszukiwarka" + "Folder" + "Folder aktywny" "Widget" "Tapeta" "Folder" "Zegar" "Ramka obrazu" - "Szukaj" + "Wyszukiwarka" "Brak miejsca na tej stronie głównej" + "Wybierz skrót" + "Wybierz folder aktywny" "Dodaj" "Tapeta" "Szukaj" @@ -50,4 +54,6 @@ "zapisywanie ustawień i skrótów strony głównej" "Umożliwia aplikacji zmianę ustawień i skrótów strony głównej." "Szukaj w Google" + + diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 9bb4787c1f..ab6120e9bf 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -28,6 +28,8 @@ "Добавление на главный экран" "Приложение" "Ярлык" + "Поиск" + "Папка" "Динамическая папка" "Виджет" "Фоновый рисунок" @@ -36,6 +38,8 @@ "Рамка для картинки" "Поиск" "На главном экране больше нет места." + "Выберите ярлык" + "Выберите активную папку" "Добавить" "Фоновый рисунок" "Искать" @@ -50,4 +54,6 @@ "записывать ярлыки и настройки главного экрана" "Позволяет приложению изменять настройки и ярлыки на главном экране." "Поиск Google" + + diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 72ffcf42a5..f5d929d16f 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -28,6 +28,8 @@ "添加到“主页”屏幕" "应用程序" "快捷键" + "搜索" + "文件夹" "活动的文件夹" "小工具" "壁纸" @@ -36,6 +38,8 @@ "相框" "搜索" "该“主页”屏幕上没有多余空间。" + "选择快捷键" + "选择活动文件夹" "添加" "壁纸" "搜索" @@ -50,4 +54,6 @@ "写入“主页”设置和快捷键" "允许应用程序更改“主页”中的设置和快捷键。" "Google 搜索" + + diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 35db6220c6..326f2a232f 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -28,6 +28,8 @@ "新增至首頁畫面" "應用程式" "捷徑" + "搜尋" + "資料夾" "使用中的資料夾" "Widget" "桌布" @@ -36,6 +38,8 @@ "相框" "搜尋" "首頁已無空間" + "選取捷徑" + "選取作用中資料夾" "新增" "桌布" "搜尋" @@ -50,4 +54,6 @@ "寫入首頁設定和捷徑" "允許應用程式變更首頁中的設定和捷徑。" "Google 搜尋" + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 87f5b788b4..ab545aa764 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -69,4 +69,11 @@ + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml index d4051f0dbd..557494405d 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -22,4 +22,6 @@ #EB191919 #B2191919 #A5FF0000 + + #fccc diff --git a/res/values/strings.xml b/res/values/strings.xml index c1d3455ae7..a7945cba7f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -52,8 +52,13 @@ Application Shortcut + + Search + + Folder + Live folder - + Widget Wallpaper @@ -68,6 +73,11 @@ No more room on this Home screen. + + Select shortcut + + Select live folder + @@ -101,5 +111,8 @@ This translation SHOULD MATCH the string "search_hint" which is found in GoogleSearch/res/values/strings.xml --> Google Search - + + + Problem loading widget + diff --git a/src/com/android/launcher/AddAdapter.java b/src/com/android/launcher/AddAdapter.java index 8eebe39d98..cbcb3380ab 100644 --- a/src/com/android/launcher/AddAdapter.java +++ b/src/com/android/launcher/AddAdapter.java @@ -16,456 +16,111 @@ package com.android.launcher; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.TextView; -import android.widget.BaseExpandableListAdapter; -import android.graphics.drawable.Drawable; -import android.provider.LiveFolders; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; /** - * Shows a list of all the items that can be added to the workspace. + * Adapter showing the types of items that can be added to a {@link Workspace}. */ -public final class AddAdapter extends BaseExpandableListAdapter { - private static final int GROUP_APPLICATIONS = 0; - private static final int GROUP_SHORTCUTS = 1; - private static final int GROUP_WIDGETS = 2; - private static final int GROUP_LIVE_FOLDERS = 3; - private static final int GROUP_WALLPAPERS = 4; - - private final Intent mCreateShortcutIntent; - private final Intent mCreateLiveFolderIntent; - private Intent mSetWallpaperIntent; +public class AddAdapter extends BaseAdapter { + + private final Launcher mLauncher; private final LayoutInflater mInflater; - private Launcher mLauncher; - private Group[] mGroups; - - /** - * Abstract class representing one thing that can be added - */ - public abstract class AddAction implements Runnable { - protected final Context mContext; - - AddAction(Context context) { - mContext = context; - } - - Drawable getIcon(int resource) { - return mContext.getResources().getDrawable(resource); - } - - public abstract void bindView(View v); - } - - /** - * Class representing an action that will create set the wallpaper. - */ - public class SetWallpaperAction extends CreateShortcutAction { - SetWallpaperAction(Context context, ResolveInfo info) { - super(context, info); - } - - public void run() { - Intent intent = new Intent(mSetWallpaperIntent); - ActivityInfo activityInfo = mInfo.activityInfo; - intent.setComponent(new ComponentName(activityInfo.applicationInfo.packageName, - activityInfo.name)); - mLauncher.startActivity(intent); - } - } + + private final ArrayList mItems = new ArrayList(); + + public static final int ITEM_APPLICATION = 0; + public static final int ITEM_SHORTCUT = 1; + public static final int ITEM_SEARCH = 2; + public static final int ITEM_APPWIDGET = 3; + public static final int ITEM_LIVE_FOLDER = 4; + public static final int ITEM_FOLDER = 5; + public static final int ITEM_WALLPAPER = 6; /** - * Class representing an action that will create a specific type - * of shortcut + * Specific item in our list. */ - public class CreateShortcutAction extends AddAction { + public class ListItem { + public final CharSequence text; + public final Drawable image; + public final int actionTag; - ResolveInfo mInfo; - private CharSequence mLabel; - private Drawable mIcon; - - CreateShortcutAction(Context context, ResolveInfo info) { - super(context); - mInfo = info; - } - - @Override - public void bindView(View view) { - ResolveInfo info = mInfo; - TextView text = (TextView) view; - - PackageManager pm = mLauncher.getPackageManager(); - - if (mLabel == null) { - mLabel = info.loadLabel(pm); - if (mLabel == null) { - mLabel = info.activityInfo.name; - } + public ListItem(Resources res, int textResourceId, int imageResourceId, int actionTag) { + text = res.getString(textResourceId); + if (imageResourceId != -1) { + image = res.getDrawable(imageResourceId); + } else { + image = null; } - - if (mIcon == null) { - mIcon = Utilities.createIconThumbnail(info.loadIcon(pm), mContext); - } - - text.setText(mLabel); - text.setCompoundDrawablesWithIntrinsicBounds(mIcon, null, null, null); - } - - public void run() { - Intent intent = new Intent(mCreateShortcutIntent); - ActivityInfo activityInfo = mInfo.activityInfo; - intent.setComponent(new ComponentName(activityInfo.applicationInfo.packageName, - activityInfo.name)); - mLauncher.addShortcut(intent); - } - } - - /** - * Class representing an action that will create a specific type - * of live folder - */ - public class CreateLiveFolderAction extends CreateShortcutAction { - CreateLiveFolderAction(Context context, ResolveInfo info) { - super(context, info); - } - - @Override - public void run() { - Intent intent = new Intent(mCreateLiveFolderIntent); - ActivityInfo activityInfo = mInfo.activityInfo; - intent.setComponent(new ComponentName(activityInfo.applicationInfo.packageName, - activityInfo.name)); - mLauncher.addLiveFolder(intent); - } - } - - /** - * Class representing an action that will add a folder - */ - public class CreateFolderAction extends AddAction { - private Drawable mIcon; - - CreateFolderAction(Context context) { - super(context); - } - - @Override - public void bindView(View view) { - TextView text = (TextView) view; - text.setText(R.string.add_folder); - if (mIcon == null) mIcon = getIcon(R.drawable.ic_launcher_folder); - text.setCompoundDrawablesWithIntrinsicBounds(mIcon, null, null, null); - } - - public void run() { - mLauncher.addFolder(); - } - } - - /** - * Class representing an action that will add a folder - */ - public class CreateClockAction extends AddAction { - - CreateClockAction(Context context) { - super(context); - } - - @Override - public void bindView(View view) { - TextView text = (TextView) view; - text.setText(R.string.add_clock); - Drawable icon = getIcon(R.drawable.ic_launcher_alarmclock); - text.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - } - - public void run() { - mLauncher.addClock(); - } - } - - /** - * Class representing an action that will add a PhotoFrame - */ - public class CreatePhotoFrameAction extends AddAction { - private Drawable mIcon; - - CreatePhotoFrameAction(Context context) { - super(context); - } - - @Override - public void bindView(View view) { - TextView text = (TextView) view; - text.setText(R.string.add_photo_frame); - if (mIcon == null) mIcon = getIcon(R.drawable.ic_launcher_gallery); - text.setCompoundDrawablesWithIntrinsicBounds(mIcon, null, null, null); - } - - public void run() { - mLauncher.getPhotoForPhotoFrame(); - } - } - - - /** - * Class representing an action that will add a Search widget - */ - public class CreateSearchAction extends AddAction { - private Drawable mIcon; - - CreateSearchAction(Context context) { - super(context); - } - - @Override - public void bindView(View view) { - TextView text = (TextView) view; - text.setText(R.string.add_search); - if (mIcon == null) mIcon = getIcon(R.drawable.ic_search_gadget); - text.setCompoundDrawablesWithIntrinsicBounds(mIcon, null, null, null); - } - - public void run() { - mLauncher.addSearch(); + this.actionTag = actionTag; } } - private class Group { - private String mName; - private ArrayList mList; - - Group(String name) { - mName = name; - mList = new ArrayList(); - } - - void add(AddAction action) { - mList.add(action); - } - - int size() { - return mList.size(); - } - - String getName() { - return mName; - } - - void run(int position) { - mList.get(position).run(); - } - - void bindView(int childPosition, View view) { - mList.get(childPosition).bindView(view); - } - - public Object get(int childPosition) { - return mList.get(childPosition); - } - } - - private class ApplicationsGroup extends Group { - private final Launcher mLauncher; - private final ArrayList mApplications; - - ApplicationsGroup(Launcher launcher, String name) { - super(name); - mLauncher = launcher; - mApplications = Launcher.getModel().getApplications(); - } - - @Override - int size() { - return mApplications == null ? 0 : mApplications.size(); - } - - @Override - void add(AddAction action) { - } - - @Override - void run(int position) { - final ApplicationInfo info = mApplications.get(position); - mLauncher.addApplicationShortcut(info); - } - - @Override - void bindView(int childPosition, View view) { - TextView text = (TextView) view.findViewById(R.id.title); - - final ApplicationInfo info = mApplications.get(childPosition); - text.setText(info.title); - if (!info.filtered) { - info.icon = Utilities.createIconThumbnail(info.icon, mLauncher); - info.filtered = true; - } - text.setCompoundDrawablesWithIntrinsicBounds(info.icon, null, null, null); - } - - @Override - public Object get(int childPosition) { - return mApplications.get(childPosition); - } - } - - public AddAdapter(Launcher launcher, boolean forFolder) { - mCreateShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); - mCreateShortcutIntent.setComponent(null); - - mCreateLiveFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER); - mCreateLiveFolderIntent.setComponent(null); - - mSetWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER); - mSetWallpaperIntent.setComponent(null); - + public AddAdapter(Launcher launcher) { + super(); + mLauncher = launcher; - mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mInflater = (LayoutInflater) mLauncher.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + // Create default actions + Resources res = launcher.getResources(); + + mItems.add(new ListItem(res, R.string.group_applications, + R.drawable.ic_launcher_application, ITEM_APPLICATION)); + + mItems.add(new ListItem(res, R.string.group_shortcuts, + R.drawable.ic_launcher_empty, ITEM_SHORTCUT)); + + mItems.add(new ListItem(res, R.string.group_search, + R.drawable.ic_search_widget, ITEM_SEARCH)); + + mItems.add(new ListItem(res, R.string.group_widgets, + R.drawable.ic_launcher_appwidget, ITEM_APPWIDGET)); + + mItems.add(new ListItem(res, R.string.group_live_folders, + R.drawable.ic_launcher_empty, ITEM_LIVE_FOLDER)); + + mItems.add(new ListItem(res, R.string.group_folder, + R.drawable.ic_launcher_folder, ITEM_FOLDER)); + + mItems.add(new ListItem(res, R.string.group_wallpapers, + R.drawable.ic_launcher_gallery, ITEM_WALLPAPER)); - mGroups = new Group[forFolder ? 2 : 5]; - final Group[] groups = mGroups; - groups[GROUP_APPLICATIONS] = new ApplicationsGroup(mLauncher, - mLauncher.getString(R.string.group_applications)); - groups[GROUP_SHORTCUTS] = new Group(mLauncher.getString(R.string.group_shortcuts)); - groups[GROUP_LIVE_FOLDERS] = new Group(mLauncher.getString(R.string.group_live_folders)); + } - if (!forFolder) { - groups[GROUP_WALLPAPERS] = new Group(mLauncher.getString(R.string.group_wallpapers)); - groups[GROUP_SHORTCUTS].add(new CreateFolderAction(launcher)); - groups[GROUP_WIDGETS] = new Group(mLauncher.getString(R.string.group_widgets)); - - final Group widgets = groups[GROUP_WIDGETS]; - widgets.add(new CreateClockAction(launcher)); - widgets.add(new CreatePhotoFrameAction(launcher)); - widgets.add(new CreateSearchAction(launcher)); + public View getView(int position, View convertView, ViewGroup parent) { + ListItem item = (ListItem) getItem(position); + + if (convertView == null) { + convertView = mInflater.inflate(R.layout.add_list_item, parent, false); } - PackageManager packageManager = launcher.getPackageManager(); - - List list = findTargetsForIntent(mCreateShortcutIntent, packageManager); - if (list != null && list.size() > 0) { - int count = list.size(); - final Group shortcuts = groups[GROUP_SHORTCUTS]; - for (int i = 0; i < count; i++) { - ResolveInfo resolveInfo = list.get(i); - shortcuts.add(new CreateShortcutAction(launcher, resolveInfo)); - } - } - - list = findTargetsForIntent(mCreateLiveFolderIntent, packageManager); - if (list != null && list.size() > 0) { - int count = list.size(); - final Group shortcuts = groups[GROUP_LIVE_FOLDERS]; - for (int i = 0; i < count; i++) { - ResolveInfo resolveInfo = list.get(i); - shortcuts.add(new CreateLiveFolderAction(launcher, resolveInfo)); - } - } - - list = findTargetsForIntent(mSetWallpaperIntent, packageManager); - if (list != null && list.size() > 0) { - int count = list.size(); - final Group shortcuts = groups[GROUP_WALLPAPERS]; - for (int i = 0; i < count; i++) { - ResolveInfo resolveInfo = list.get(i); - shortcuts.add(new SetWallpaperAction(launcher, resolveInfo)); - } - } + TextView textView = (TextView) convertView; + textView.setTag(item); + textView.setText(item.text); + textView.setCompoundDrawablesWithIntrinsicBounds(item.image, null, null, null); + + return convertView; } - private List findTargetsForIntent(Intent intent, PackageManager packageManager) { - List list = packageManager.queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY); - if (list != null) { - int count = list.size(); - if (count > 1) { - // Only display the first matches that are either of equal - // priority or have asked to be default options. - ResolveInfo firstInfo = list.get(0); - for (int i=1; i= 0 && cellXY[1] >= 0 && cellXY[0] < xCount && cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]]; - if (cellInfo.valid) { - findIntersectingVacantCells(cellInfo, cellXY[0], cellXY[1], - xCount, yCount, occupied); - } + // Instead of finding the interesting vacant cells here, wait until a + // caller invokes getTag() to retrieve the result. Finding the vacant + // cells is a bit expensive and can generate many new objects, it's + // therefore better to defer it until we know we actually need it. + + mDirtyTag = true; } setTag(cellInfo); } else if (action == MotionEvent.ACTION_UP) { @@ -194,12 +199,31 @@ public class CellLayout extends ViewGroup { cellInfo.spanX = 0; cellInfo.spanY = 0; cellInfo.valid = false; + mDirtyTag = false; setTag(cellInfo); } return false; } + @Override + public CellInfo getTag() { + final CellInfo info = (CellInfo) super.getTag(); + if (mDirtyTag && info.valid) { + final boolean portrait = mPortrait; + final int xCount = portrait ? mShortAxisCells : mLongAxisCells; + final int yCount = portrait ? mLongAxisCells : mShortAxisCells; + + final boolean[][] occupied = mOccupied; + findOccupiedCells(xCount, yCount, occupied); + + findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied); + + mDirtyTag = false; + } + return info; + } + private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y, int xCount, int yCount, boolean[][] occupied) { @@ -207,14 +231,15 @@ public class CellLayout extends ViewGroup { cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE; cellInfo.maxVacantSpanY = Integer.MIN_VALUE; cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE; - cellInfo.vacantCells = new ArrayList(); + cellInfo.clearVacantCells(); if (occupied[x][y]) { return; } - Rect current = new Rect(x, y, x, y); - findVacantCell(current, xCount, yCount, occupied, cellInfo); + cellInfo.current.set(x, y, x, y); + + findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo); } private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied, @@ -256,7 +281,7 @@ public class CellLayout extends ViewGroup { } private static void addVacantCell(Rect current, CellInfo cellInfo) { - CellInfo.VacantCell cell = new CellInfo.VacantCell(); + CellInfo.VacantCell cell = CellInfo.VacantCell.acquire(); cell.cellX = current.left; cell.cellY = current.top; cell.spanX = current.right - current.left + 1; @@ -317,10 +342,9 @@ public class CellLayout extends ViewGroup { cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE; cellInfo.maxVacantSpanY = Integer.MIN_VALUE; cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE; - cellInfo.vacantCells = new ArrayList(); cellInfo.screen = mCellInfo.screen; - Rect current = new Rect(); + Rect current = cellInfo.current; for (int x = 0; x < xCount; x++) { for (int y = 0; y < yCount; y++) { @@ -333,16 +357,10 @@ public class CellLayout extends ViewGroup { } cellInfo.valid = cellInfo.vacantCells.size() > 0; - if (cellInfo.valid) { - int[] xy = new int[2]; - if (cellInfo.findCellForSpan(xy, 1, 1)) { - cellInfo.cellX = xy[0]; - cellInfo.cellY = xy[1]; - cellInfo.spanY = 1; - cellInfo.spanX = 1; - } - } + // Assume the caller will perform their own cell searching, otherwise we + // risk causing an unnecessary rebuild after findCellForSpan() + return cellInfo; } @@ -634,6 +652,26 @@ public class CellLayout extends ViewGroup { dragRect.set(x, y, x + width, y + height); } + + /** + * Computes the required horizontal and vertical cell spans to always + * fit the given rectangle. + * + * @param width Width in pixels + * @param height Height in pixels + */ + public int[] rectToCell(int width, int height) { + // Always assume we're working with the smallest span to make sure we + // reserve enough space in both orientations. + int actualWidth = mCellWidth + mWidthGap; + int actualHeight = mCellHeight + mHeightGap; + int smallerSize = Math.min(actualWidth, actualHeight); + + // Always round up to next largest cell + int spanX = (width + smallerSize) / smallerSize; + int spanY = (height + smallerSize) / smallerSize; + return new int[] { spanX, spanY }; + } /** * Find the first vacant cell, if there is one. @@ -811,12 +849,54 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { } static final class CellInfo implements ContextMenu.ContextMenuInfo { + /** + * See View.AttachInfo.InvalidateInfo for futher explanations about + * the recycling mechanism. In this case, we recycle the vacant cells + * instances because up to several hundreds can be instanciated when + * the user long presses an empty cell. + */ static final class VacantCell { int cellX; int cellY; int spanX; int spanY; + // We can create up to 523 vacant cells on a 4x4 grid, 100 seems + // like a reasonable compromise given the size of a VacantCell and + // the fact that the user is not likely to touch an empty 4x4 grid + // very often + private static final int POOL_LIMIT = 100; + private static final Object sLock = new Object(); + + private static int sAcquiredCount = 0; + private static VacantCell sRoot; + + private VacantCell next; + + static VacantCell acquire() { + synchronized (sLock) { + if (sRoot == null) { + return new VacantCell(); + } + + VacantCell info = sRoot; + sRoot = info.next; + sAcquiredCount--; + + return info; + } + } + + void release() { + synchronized (sLock) { + if (sAcquiredCount < POOL_LIMIT) { + sAcquiredCount++; + next = sRoot; + sRoot = this; + } + } + } + @Override public String toString() { return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX + @@ -832,17 +912,27 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { int screen; boolean valid; - ArrayList vacantCells; + final ArrayList vacantCells = new ArrayList(VacantCell.POOL_LIMIT); int maxVacantSpanX; int maxVacantSpanXSpanY; int maxVacantSpanY; int maxVacantSpanYSpanX; + final Rect current = new Rect(); + + private void clearVacantCells() { + final ArrayList list = vacantCells; + final int count = list.size(); + + for (int i = 0; i < count; i++) list.get(i).release(); + + list.clear(); + } void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) { if (cellX < 0 || cellY < 0) { maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE; maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE; - vacantCells = new ArrayList(); + clearVacantCells(); return; } @@ -855,26 +945,40 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened); } + /** + * This method can be called only once! Calling #findVacantCellsFromOccupied will + * restore the ability to call this method. + * + * Finds the upper-left coordinate of the first rectangle in the grid that can + * hold a cell of the specified dimensions. + * + * @param cellXY The array that will contain the position of a vacant cell if such a cell + * can be found. + * @param spanX The horizontal span of the cell we want to find. + * @param spanY The vertical span of the cell we want to find. + * + * @return True if a vacant cell of the specified dimension was found, false otherwise. + */ boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { - if (vacantCells == null) { - return false; - } + final ArrayList list = vacantCells; + final int count = list.size(); + + boolean found = false; if (this.spanX >= spanX && this.spanY >= spanY) { cellXY[0] = cellX; cellXY[1] = cellY; - return true; + found = true; } - final ArrayList list = vacantCells; - final int count = list.size(); // Look for an exact match first for (int i = 0; i < count; i++) { VacantCell cell = list.get(i); if (cell.spanX == spanX && cell.spanY == spanY) { cellXY[0] = cell.cellX; cellXY[1] = cell.cellY; - return true; + found = true; + break; } } @@ -884,11 +988,14 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { if (cell.spanX >= spanX && cell.spanY >= spanY) { cellXY[0] = cell.cellX; cellXY[1] = cell.cellY; - return true; + found = true; + break; } } - return false; + clearVacantCells(); + + return found; } @Override diff --git a/src/com/android/launcher/DeleteZone.java b/src/com/android/launcher/DeleteZone.java index 798cf0de8c..7f92c2334d 100644 --- a/src/com/android/launcher/DeleteZone.java +++ b/src/com/android/launcher/DeleteZone.java @@ -85,7 +85,11 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. final LauncherModel model = Launcher.getModel(); if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - model.removeDesktopItem(item); + if (item instanceof LauncherAppWidgetInfo) { + model.removeDesktopAppWidget((LauncherAppWidgetInfo) item); + } else { + model.removeDesktopItem(item); + } } else { if (source instanceof UserFolder) { final UserFolder userFolder = (UserFolder) source; @@ -97,6 +101,12 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. final UserFolderInfo userFolderInfo = (UserFolderInfo)item; LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo); model.removeUserFolder(userFolderInfo); + } else if (item instanceof LauncherAppWidgetInfo) { + final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item; + final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost(); + if (appWidgetHost != null) { + appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId); + } } LauncherModel.deleteItemFromDatabase(mLauncher, item); } diff --git a/src/com/android/launcher/DragLayer.java b/src/com/android/launcher/DragLayer.java index 56140dd076..b542de62aa 100644 --- a/src/com/android/launcher/DragLayer.java +++ b/src/com/android/launcher/DragLayer.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.KeyEvent; +import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; /** @@ -127,6 +128,8 @@ public class DragLayer extends FrameLayout implements DragController { private int mAnimationType; private int mAnimationState = ANIMATION_STATE_DONE; + private InputMethodManager mInputMethodManager; + /** * Used to create a new DragLayer from XML. * @@ -144,7 +147,14 @@ public class DragLayer extends FrameLayout implements DragController { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); } - + + // Hide soft keyboard, if visible + if (mInputMethodManager == null) { + mInputMethodManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + } + mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + if (mListener != null) { mListener.onDragStart(v, source, dragInfo, dragAction); } @@ -320,40 +330,33 @@ public class DragLayer extends FrameLayout implements DragController { break; case MotionEvent.ACTION_MOVE: - if (Launcher.sOpenGlEnabled) { - mLastMotionX = x; - mLastMotionY = y; + final int scrollX = mScrollX; + final int scrollY = mScrollY; - invalidate(); - } else { - final int scrollX = mScrollX; - final int scrollY = mScrollY; + final float touchX = mTouchOffsetX; + final float touchY = mTouchOffsetY; - final float touchX = mTouchOffsetX; - final float touchY = mTouchOffsetY; + final int offsetX = mBitmapOffsetX; + final int offsetY = mBitmapOffsetY; - final int offsetX = mBitmapOffsetX; - final int offsetY = mBitmapOffsetY; + int left = (int) (scrollX + mLastMotionX - touchX - offsetX); + int top = (int) (scrollY + mLastMotionY - touchY - offsetY); - int left = (int) (scrollX + mLastMotionX - touchX - offsetX); - int top = (int) (scrollY + mLastMotionY - touchY - offsetY); + final Bitmap dragBitmap = mDragBitmap; + final int width = dragBitmap.getWidth(); + final int height = dragBitmap.getHeight(); - final Bitmap dragBitmap = mDragBitmap; - final int width = dragBitmap.getWidth(); - final int height = dragBitmap.getHeight(); + final Rect rect = mRect; + rect.set(left - 1, top - 1, left + width + 1, top + height + 1); - final Rect rect = mRect; - rect.set(left - 1, top - 1, left + width + 1, top + height + 1); + mLastMotionX = x; + mLastMotionY = y; - mLastMotionX = x; - mLastMotionY = y; + left = (int) (scrollX + x - touchX - offsetX); + top = (int) (scrollY + y - touchY - offsetY); - left = (int) (scrollX + x - touchX - offsetX); - top = (int) (scrollY + y - touchY - offsetY); - - rect.union(left - 1, top - 1, left + width + 1, top + height + 1); - invalidate(rect); - } + rect.union(left - 1, top - 1, left + width + 1, top + height + 1); + invalidate(rect); final int[] coordinates = mDropCoordinates; DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); diff --git a/src/com/android/launcher/ItemInfo.java b/src/com/android/launcher/ItemInfo.java index 61745dda0e..51449a7c93 100644 --- a/src/com/android/launcher/ItemInfo.java +++ b/src/com/android/launcher/ItemInfo.java @@ -38,10 +38,8 @@ class ItemInfo { /** * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_USER_FOLDER}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_WIDGET_CLOCK}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_WIDGET_SEARCH} or - * {@link LauncherSettings.Favorites#ITEM_TYPE_WIDGET_PHOTO_FRAME}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_USER_FOLDER}, or + * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}. */ int itemType; diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java index 465400b8d8..17f16a7a3d 100644 --- a/src/com/android/launcher/Launcher.java +++ b/src/com/android/launcher/Launcher.java @@ -24,17 +24,21 @@ import android.app.SearchManager; import android.app.StatusBarManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; @@ -64,15 +68,16 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.View.OnLongClickListener; import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; import android.widget.EditText; -import android.widget.ExpandableListView; -import android.widget.ImageView; +import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import android.widget.GridView; +import android.widget.SlidingDrawer; import android.app.IWallpaperService; - -import com.android.internal.widget.SlidingDrawer; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -82,12 +87,12 @@ import java.util.ArrayList; */ public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener { static final String LOG_TAG = "Launcher"; + static final boolean LOGD = false; private static final boolean PROFILE_STARTUP = false; + private static final boolean PROFILE_DRAWER = false; private static final boolean DEBUG_USER_INTERFACE = false; - private static final boolean REMOVE_SHORTCUT_ON_PACKAGE_REMOVE = false; - private static final int WALLPAPER_SCREENS_SPAN = 2; private static final int MENU_GROUP_ADD = 1; @@ -98,9 +103,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1; private static final int REQUEST_CREATE_SHORTCUT = 1; - private static final int REQUEST_CHOOSE_PHOTO = 2; - private static final int REQUEST_UPDATE_PHOTO = 3; private static final int REQUEST_CREATE_LIVE_FOLDER = 4; + private static final int REQUEST_CREATE_APPWIDGET = 5; + private static final int REQUEST_PICK_APPLICATION = 6; + private static final int REQUEST_PICK_SHORTCUT = 7; + private static final int REQUEST_PICK_LIVE_FOLDER = 8; + private static final int REQUEST_PICK_APPWIDGET = 9; static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; @@ -144,14 +152,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On // Type: long private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; - private static LauncherModel sModel; + private static final LauncherModel sModel = new LauncherModel(); private static Bitmap sWallpaper; - // Indicates whether the OpenGL pipeline was enabled, either through - // USE_OPENGL_BY_DEFAULT or the system property launcher.opengl - static boolean sOpenGlEnabled; - private static final Object sLock = new Object(); private static int sScreen = DEFAULT_SCREN; @@ -164,7 +168,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On private DragLayer mDragLayer; private Workspace mWorkspace; - + + private AppWidgetManager mAppWidgetManager; + private LauncherAppWidgetHost mAppWidgetHost; + + static final int APPWIDGET_HOST_ID = 1024; + private CellLayout.CellInfo mAddItemCellInfo; private CellLayout.CellInfo mMenuAddInfo; private final int[] mCellCoordinates = new int[2]; @@ -172,6 +181,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On private SlidingDrawer mDrawer; private TransitionDrawable mHandleIcon; + private HandleView mHandleView; private AllAppsGridView mAllAppsGrid; private boolean mDesktopLocked = true; @@ -185,11 +195,18 @@ public final class Launcher extends Activity implements View.OnClickListener, On private boolean mWaitingForResult; private boolean mLocaleChanged; + private Bundle mSavedInstanceState; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInflater = getLayoutInflater(); - + + mAppWidgetManager = AppWidgetManager.getInstance(this); + + mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); + mAppWidgetHost.startListening(); + if (PROFILE_STARTUP) { android.os.Debug.startMethodTracing("/sdcard/launcher"); } @@ -197,10 +214,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On checkForLocaleChange(); setWallpaperDimension(); - if (sModel == null) { - sModel = new LauncherModel(); - } - setContentView(R.layout.launcher); setupViews(); @@ -260,8 +273,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On } private void startLoaders() { - sModel.loadApplications(true, this, mLocaleChanged); - sModel.loadUserItems(!mLocaleChanged, this, mLocaleChanged, true); + boolean loadApplications = sModel.loadApplications(true, this, mLocaleChanged); + sModel.loadUserItems(!mLocaleChanged, this, mLocaleChanged, loadApplications); + mRestoring = false; } @@ -283,20 +297,42 @@ public final class Launcher extends Activity implements View.OnClickListener, On @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // The pattern used here is that a user PICKs a specific application, + // which, depending on the target, might need to CREATE the actual target. + + // For example, the user would PICK_SHORTCUT for "Music playlist", and we + // launch over to the Music app to actually CREATE_SHORTCUT. + if (resultCode == RESULT_OK && mAddItemCellInfo != null) { switch (requestCode) { + case REQUEST_PICK_APPLICATION: + completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked); + break; + case REQUEST_PICK_SHORTCUT: + addShortcut(data); + break; case REQUEST_CREATE_SHORTCUT: completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked); break; - case REQUEST_CHOOSE_PHOTO: - completeAddPhotoFrame(data, mAddItemCellInfo); - break; - case REQUEST_UPDATE_PHOTO: - completeUpdatePhotoFrame(data, mAddItemCellInfo); + case REQUEST_PICK_LIVE_FOLDER: + addLiveFolder(data); break; case REQUEST_CREATE_LIVE_FOLDER: completeAddLiveFolder(data, mAddItemCellInfo, !mDesktopLocked); break; + case REQUEST_PICK_APPWIDGET: + addAppWidget(data); + break; + case REQUEST_CREATE_APPWIDGET: + completeAddAppWidget(data, mAddItemCellInfo, !mDesktopLocked); + break; + } + } else if (requestCode == REQUEST_PICK_APPWIDGET && + resultCode == RESULT_CANCELED && data != null) { + // Clean up the appWidgetId if we canceled + int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + if (appWidgetId != -1) { + mAppWidgetHost.deleteAppWidgetId(appWidgetId); } } mWaitingForResult = false; @@ -312,19 +348,21 @@ public final class Launcher extends Activity implements View.OnClickListener, On } @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - boolean handled = super.onKeyUp(keyCode, event); - if (keyCode == KeyEvent.KEYCODE_SEARCH) { - handled = mWorkspace.snapToSearch(); - if (handled) closeDrawer(true); - } - return handled; + protected void onPause() { + super.onPause(); + closeDrawer(false); + } + + private boolean acceptFilter() { + final InputMethodManager inputManager = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + return !inputManager.isFullscreenMode(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { boolean handled = super.onKeyDown(keyCode, event); - if (!handled && keyCode != KeyEvent.KEYCODE_ENTER) { + if (!handled && acceptFilter() && keyCode != KeyEvent.KEYCODE_ENTER) { boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb, keyCode, event); if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) { @@ -398,7 +436,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On mRestoring = true; } - boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); if (renameFolder) { long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); @@ -425,9 +462,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On final DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone); - final HandleView handleIcon = (HandleView) drawer.findViewById(R.id.all_apps); - handleIcon.setLauncher(this); - mHandleIcon = (TransitionDrawable) handleIcon.getDrawable(); + mHandleView = (HandleView) drawer.findViewById(R.id.all_apps); + mHandleView.setLauncher(this); + mHandleIcon = (TransitionDrawable) mHandleView.getDrawable(); mHandleIcon.setCrossFadeEnabled(true); drawer.lock(); @@ -439,10 +476,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On grid.setTextFilterEnabled(true); grid.setDragger(dragLayer); grid.setLauncher(this); - if (sOpenGlEnabled) { - grid.setScrollingCacheEnabled(false); - grid.setFadingEdgeLength(0); - } workspace.setOnLongClickListener(this); workspace.setDragger(dragLayer); @@ -451,23 +484,11 @@ public final class Launcher extends Activity implements View.OnClickListener, On deleteZone.setLauncher(this); deleteZone.setDragController(dragLayer); - deleteZone.setHandle(handleIcon); + deleteZone.setHandle(mHandleView); dragLayer.setIgnoredDropTarget(grid); dragLayer.setDragScoller(workspace); dragLayer.setDragListener(deleteZone); - - if (DEBUG_USER_INTERFACE) { - android.widget.Button finishButton = new android.widget.Button(this); - finishButton.setText("Finish"); - workspace.addInScreen(finishButton, 1, 0, 0, 1, 1); - - finishButton.setOnClickListener(new android.widget.Button.OnClickListener() { - public void onClick(View v) { - finish(); - } - }); - } } /** @@ -507,11 +528,44 @@ public final class Launcher extends Activity implements View.OnClickListener, On return favorite; } - void addApplicationShortcut(ApplicationInfo info) { - mAddItemCellInfo.screen = mWorkspace.getCurrentScreen(); - mWorkspace.addApplicationShortcut(info, mAddItemCellInfo); - } + /** + * Add an application shortcut to the workspace. + * + * @param data The intent describing the application. + * @param cellInfo The position on screen where to create the shortcut. + */ + void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo, + boolean insertAtFirst) { + cellInfo.screen = mWorkspace.getCurrentScreen(); + if (!findSingleSlot(cellInfo)) return; + // Find details for this application + ComponentName component = data.getComponent(); + PackageManager packageManager = context.getPackageManager(); + ActivityInfo activityInfo = null; + try { + activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e); + } + + if (activityInfo != null) { + ApplicationInfo itemInfo = new ApplicationInfo(); + + itemInfo.title = activityInfo.loadLabel(packageManager); + if (itemInfo.title == null) { + itemInfo.title = activityInfo.name; + } + + itemInfo.setActivity(component, Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + itemInfo.icon = activityInfo.loadIcon(packageManager); + itemInfo.container = ItemInfo.NO_ID; + + mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst); + } + } + /** * Add a shortcut to the workspace. * @@ -521,8 +575,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On */ private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo, boolean insertAtFirst) { - cellInfo.screen = mWorkspace.getCurrentScreen(); + if (!findSingleSlot(cellInfo)) return; + final ApplicationInfo info = addShortcut(this, data, cellInfo, false); if (!mRestoring) { @@ -535,6 +590,60 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } + + /** + * Add a widget to the workspace. + * + * @param data The intent describing the appWidgetId. + * @param cellInfo The position on screen where to create the widget. + */ + private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo, + boolean insertAtFirst) { + + Bundle extras = data.getExtras(); + int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + + Log.d(LOG_TAG, "dumping extras content="+extras.toString()); + + AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + + // Calculate the grid spans needed to fit this widget + CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen); + int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight); + + // Try finding open space on Launcher screen + final int[] xy = mCellCoordinates; + if (!findSlot(cellInfo, xy, spans[0], spans[1])) return; + + // Build Launcher-specific widget info and save to database + LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId); + launcherInfo.spanX = spans[0]; + launcherInfo.spanY = spans[1]; + + LauncherModel.addItemToDatabase(this, launcherInfo, + LauncherSettings.Favorites.CONTAINER_DESKTOP, + mWorkspace.getCurrentScreen(), xy[0], xy[1], false); + + if (!mRestoring) { + sModel.addDesktopAppWidget(launcherInfo); + + // Perform actual inflation because we're live + launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + + launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo); + launcherInfo.hostView.setTag(launcherInfo); + + mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1], + launcherInfo.spanX, launcherInfo.spanY, insertAtFirst); + } else if (sModel.isDesktopLoaded()) { + sModel.addDesktopAppWidget(launcherInfo); + } + } + + public LauncherAppWidgetHost getAppWidgetHost() { + return mAppWidgetHost; + } + static ApplicationInfo addShortcut(Context context, Intent data, CellLayout.CellInfo cellInfo, boolean notify) { @@ -584,107 +693,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On return info; } - /** - * Add a PhotFrame to the workspace. - * - * @param data The intent describing the photo. - * @param cellInfo The position on screen where to create the shortcut. - */ - private void completeAddPhotoFrame(Intent data, CellLayout.CellInfo cellInfo) { - final Bundle extras = data.getExtras(); - if (extras != null) { - Bitmap photo = extras.getParcelable("data"); - - Widget info = Widget.makePhotoFrame(); - info.photo = photo; - - final int[] xy = mCellCoordinates; - if (!findSlot(cellInfo, xy, info.spanX, info.spanY)) return; - - LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), xy[0], xy[1], false); - - if (!mRestoring) { - sModel.addDesktopItem(info); - - final PhotoFrame view = (PhotoFrame) mInflater.inflate(info.layoutResource, null); - view.setImageBitmap(photo); - view.setTag(info); - - mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, info.spanY); - } else if (sModel.isDesktopLoaded()) { - sModel.addDesktopItem(info); - } - } - } - - /** - * Updates a workspace PhotoFrame. - * - * @param data The intent describing the photo. - * @param cellInfo The position on screen of the PhotoFrame to update. - */ - private void completeUpdatePhotoFrame(Intent data, CellLayout.CellInfo cellInfo) { - final Bundle extras = data.getExtras(); - if (extras != null) { - Widget info; - Bitmap photo = extras.getParcelable("data"); - - if (!mRestoring) { - final CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen); - final PhotoFrame view = (PhotoFrame) layout.findCell(cellInfo.cellX, cellInfo.cellY, - cellInfo.spanX, cellInfo.spanY, null); - view.setImageBitmap(photo); - info = (Widget) view.getTag(); - } else { - info = LauncherModel.getPhotoFrameInfo(this, cellInfo.screen, - cellInfo.cellX, cellInfo.cellY); - } - - info.photo = photo; - LauncherModel.updateItemInDatabase(this, info); - } - } - - /** - * Starts a new Intent to let the user update the PhotoFrame defined by the - * specified Widget. - * - * @param widget The Widget info defining which PhotoFrame to update. - */ - void updatePhotoFrame(Widget widget) { - CellLayout.CellInfo info = new CellLayout.CellInfo(); - info.screen = widget.screen; - info.cellX = widget.cellX; - info.cellY = widget.cellY; - info.spanX = widget.spanX; - info.spanY = widget.spanY; - mAddItemCellInfo = info; - - startActivityForResult(createPhotoPickIntent(), Launcher.REQUEST_UPDATE_PHOTO); - } - - /** - * Creates an Intent used to let the user pick a photo for a PhotoFrame. - * - * @return The Intent to pick a photo suited for a PhotoFrame. - */ - private static Intent createPhotoPickIntent() { - // TODO: Move this method to PhotoFrame? - // TODO: get these values from constants somewhere - // TODO: Adjust the PhotoFrame's image size to avoid on the fly scaling - Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); - intent.setType("image/*"); - intent.putExtra("crop", "true"); - intent.putExtra("aspectX", 1); - intent.putExtra("aspectY", 1); - intent.putExtra("outputX", 192); - intent.putExtra("outputY", 192); - intent.putExtra("noFaceDetection", true); - intent.putExtra("return-data", true); - return intent; - } - @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -729,6 +737,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + // Do not call super here + mSavedInstanceState = savedInstanceState; + } + @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentScreen()); @@ -776,6 +790,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On mDestroyed = true; super.onDestroy(); + + try { + mAppWidgetHost.stopListening(); + } catch (NullPointerException ex) { + Log.w(LOG_TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex); + } TextKeyListener.getInstance().release(); @@ -853,9 +873,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On startWallpaper(); return true; case MENU_SEARCH: - if (!mWorkspace.snapToSearch()) { - onSearchRequested(); - } + onSearchRequested(); return true; case MENU_NOTIFICATIONS: showNotifications(); @@ -865,16 +883,63 @@ public final class Launcher extends Activity implements View.OnClickListener, On return super.onOptionsItemSelected(item); } + @Override + public boolean onSearchRequested() { + if (mWorkspace.snapToSearch()) { + closeDrawer(true); // search widget: get drawer out of the way + return true; + } else { + return super.onSearchRequested(); // no search widget: use system search UI + } + } + private void addItems() { showAddDialog(mMenuAddInfo); } private void removeShortcutsForPackage(String packageName) { if (packageName != null && packageName.length() > 0) { - android.util.Log.d(LOG_TAG, packageName); mWorkspace.removeShortcutsForPackage(packageName); } } + + void addAppWidget(Intent data) { + // TODO: catch bad widget exception when sent + int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + + if (appWidget.configure != null) { + // Launch over to configure widget, if needed + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); + intent.setComponent(appWidget.configure); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + + startActivityForResult(intent, REQUEST_CREATE_APPWIDGET); + } else { + // Otherwise just add it + onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data); + } + } + + void addSearch() { + final Widget info = Widget.makeSearch(); + final CellLayout.CellInfo cellInfo = mAddItemCellInfo; + + final int[] xy = mCellCoordinates; + final int spanX = info.spanX; + final int spanY = info.spanY; + + if (!findSlot(cellInfo, xy, spanX, spanY)) return; + + sModel.addDesktopItem(info); + LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, + mWorkspace.getCurrentScreen(), xy[0], xy[1], false); + + final View view = mInflater.inflate(info.layoutResource, null); + view.setTag(info); + + mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY); + } void addShortcut(Intent intent) { startActivityForResult(intent, REQUEST_CREATE_SHORTCUT); @@ -884,28 +949,32 @@ public final class Launcher extends Activity implements View.OnClickListener, On startActivityForResult(intent, REQUEST_CREATE_LIVE_FOLDER); } - void addFolder() { + void addFolder(boolean insertAtFirst) { UserFolderInfo folderInfo = new UserFolderInfo(); folderInfo.title = getText(R.string.folder_name); - int cellX = mAddItemCellInfo.cellX; - int cellY = mAddItemCellInfo.cellY; + + CellLayout.CellInfo cellInfo = mAddItemCellInfo; + cellInfo.screen = mWorkspace.getCurrentScreen(); + if (!findSingleSlot(cellInfo)) return; // Update the model LauncherModel.addItemToDatabase(this, folderInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), cellX, cellY, false); + mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false); sModel.addDesktopItem(folderInfo); sModel.addFolder(folderInfo); // Create the view FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo); - mWorkspace.addInCurrentScreen(newFolder, cellX, cellY, 1, 1); + mWorkspace.addInCurrentScreen(newFolder, + cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst); } - + private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo, boolean insertAtFirst) { - cellInfo.screen = mWorkspace.getCurrentScreen(); + if (!findSingleSlot(cellInfo)) return; + final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false); if (!mRestoring) { @@ -964,37 +1033,14 @@ public final class Launcher extends Activity implements View.OnClickListener, On return info; } - void getPhotoForPhotoFrame() { - startActivityForResult(createPhotoPickIntent(), REQUEST_CHOOSE_PHOTO); - } - - void addClock() { - final Widget info = Widget.makeClock(); - addWidget(info); - } - - void addSearch() { - final Widget info = Widget.makeSearch(); - addWidget(info); - } - - private void addWidget(final Widget info) { - final CellLayout.CellInfo cellInfo = mAddItemCellInfo; - - final int[] xy = mCellCoordinates; - final int spanX = info.spanX; - final int spanY = info.spanY; - - if (!findSlot(cellInfo, xy, spanX, spanY)) return; - - sModel.addDesktopItem(info); - LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, - mWorkspace.getCurrentScreen(), xy[0], xy[1], false); - - final View view = mInflater.inflate(info.layoutResource, null); - view.setTag(info); - - mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY); + private boolean findSingleSlot(CellLayout.CellInfo cellInfo) { + final int[] xy = new int[2]; + if (findSlot(cellInfo, xy, 1, 1)) { + cellInfo.cellX = xy[0]; + cellInfo.cellY = xy[1]; + return true; + } + return false; } private boolean findSlot(CellLayout.CellInfo cellInfo, int[] xy, int spanX, int spanY) { @@ -1117,9 +1163,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On void onDesktopItemsLoaded() { if (mDestroyed) return; - +android.util.Log.d("Home", "setting grid adapter"); + mAllAppsGrid.setAdapter(sModel.getApplicationsAdapter()); bindDesktopItems(); - mAllAppsGrid.setAdapter(Launcher.getModel().getApplicationsAdapter()); } /** @@ -1127,7 +1173,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On */ private void bindDesktopItems() { final ArrayList shortcuts = sModel.getDesktopItems(); - if (shortcuts == null) { + final ArrayList appWidgets = sModel.getDesktopAppWidgets(); + if (shortcuts == null || appWidgets == null) { return; } @@ -1136,20 +1183,30 @@ public final class Launcher extends Activity implements View.OnClickListener, On for (int i = 0; i < count; i++) { ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout(); } + + if (DEBUG_USER_INTERFACE) { + android.widget.Button finishButton = new android.widget.Button(this); + finishButton.setText("Finish"); + workspace.addInScreen(finishButton, 1, 0, 0, 1, 1); - count = shortcuts.size(); + finishButton.setOnClickListener(new android.widget.Button.OnClickListener() { + public void onClick(View v) { + finish(); + } + }); + } - final DesktopItemsBinder binder = new DesktopItemsBinder(this, shortcuts); - binder.obtainMessage(DesktopItemsBinder.MESSAGE_BIND_ITEMS, 0, count).sendToTarget(); + final DesktopBinder binder = new DesktopBinder(this, shortcuts, appWidgets); + binder.startBindingItems(); } - private void bindItems(Launcher.DesktopItemsBinder binder, + private void bindItems(Launcher.DesktopBinder binder, ArrayList shortcuts, int start, int count) { final Workspace workspace = mWorkspace; final boolean desktopLocked = mDesktopLocked; - final int end = Math.min(start + DesktopItemsBinder.ITEMS_COUNT, count); + final int end = Math.min(start + DesktopBinder.ITEMS_COUNT, count); int i = start; for ( ; i < end; i++) { @@ -1176,11 +1233,16 @@ public final class Launcher extends Activity implements View.OnClickListener, On workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1, !desktopLocked); break; - default: + case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: + final int screen = workspace.getCurrentScreen(); + final View view = mInflater.inflate(R.layout.widget_search, + (ViewGroup) workspace.getChildAt(screen), false); + final Widget widget = (Widget) item; - final View view = createWidget(mInflater, widget); view.setTag(widget); + workspace.addWidget(view, widget, !desktopLocked); + break; } } @@ -1188,14 +1250,17 @@ public final class Launcher extends Activity implements View.OnClickListener, On if (end >= count) { finishBindDesktopItems(); + binder.startBindingAppWidgets(); } else { - binder.obtainMessage(DesktopItemsBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget(); + binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget(); } } private void finishBindDesktopItems() { if (mSavedState != null) { - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + if (!mWorkspace.hasFocus()) { + mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + } final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS); if (userFolders != null) { @@ -1208,35 +1273,67 @@ public final class Launcher extends Activity implements View.OnClickListener, On final Folder openFolder = mWorkspace.getOpenFolder(); if (openFolder != null) { openFolder.requestFocus(); - } else { - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); } } final boolean allApps = mSavedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false); if (allApps) { mDrawer.open(); - mDrawer.requestFocus(); } mSavedState = null; } + if (mSavedInstanceState != null) { + super.onRestoreInstanceState(mSavedInstanceState); + mSavedInstanceState = null; + } + + if (mDrawer.isOpened() && !mDrawer.hasFocus()) { + mDrawer.requestFocus(); + } + mDesktopLocked = false; mDrawer.unlock(); } - private View createWidget(LayoutInflater inflater, Widget widget) { - final Workspace workspace = mWorkspace; - final int screen = workspace.getCurrentScreen(); - View v = inflater.inflate(widget.layoutResource, - (ViewGroup) workspace.getChildAt(screen), false); - if (widget.itemType == LauncherSettings.Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { - ((ImageView)v).setImageBitmap(widget.photo); - } - return v; - } + private void bindAppWidgets(Launcher.DesktopBinder binder, + ArrayList appWidgets, int start, int count) { + final Workspace workspace = mWorkspace; + final boolean desktopLocked = mDesktopLocked; + + final int end = Math.min(start + DesktopBinder.APPWIDGETS_COUNT, count); + int i = start; + + for ( ; i < end; i++) { + final LauncherAppWidgetInfo item = appWidgets.get(i); + + final int appWidgetId = item.appWidgetId; + final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + + if (LOGD) Log.d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", appWidgetId, appWidgetInfo)); + + item.hostView.setAppWidget(appWidgetId, appWidgetInfo); + item.hostView.setTag(item); + + workspace.addInScreen(item.hostView, item.screen, item.cellX, + item.cellY, item.spanX, item.spanY, !desktopLocked); + } + + workspace.requestLayout(); + + if (end >= count) { + finishBindDesktopAppWidgets(); + } else { + binder.obtainMessage(DesktopBinder.MESSAGE_BIND_APPWIDGETS, i, count).sendToTarget(); + } + } + + private void finishBindDesktopAppWidgets() { + } + DragController getDragController() { return mDragLayer; } @@ -1263,6 +1360,11 @@ public final class Launcher extends Activity implements View.OnClickListener, On startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + } catch (SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(LOG_TAG, "Launcher does not have the permission to launch " + intent + + ". Make sure to create a MAIN intent-filter for the corresponding activity " + + "or use the exported attribute for this activity.", e); } } @@ -1355,13 +1457,14 @@ public final class Launcher extends Activity implements View.OnClickListener, On // This happens when long clicking an item with the dpad/trackball if (cellInfo == null) { - return false; + return true; } if (mWorkspace.allowLongPress()) { if (cellInfo.cell == null) { if (cellInfo.valid) { // User long pressed on empty space + mWorkspace.setAllowLongPress(false); showAddDialog(cellInfo); } } else { @@ -1382,10 +1485,22 @@ public final class Launcher extends Activity implements View.OnClickListener, On mDrawer.close(); } + View getDrawerHandle() { + return mHandleView; + } + boolean isDrawerDown() { return !mDrawer.isMoving() && !mDrawer.isOpened(); } + boolean isDrawerUp() { + return mDrawer.isOpened() && !mDrawer.isMoving(); + } + + boolean isDrawerMoving() { + return mDrawer.isMoving(); + } + Workspace getWorkspace() { return mWorkspace; } @@ -1508,24 +1623,24 @@ public final class Launcher extends Activity implements View.OnClickListener, On * Displays the shortcut creation dialog and launches, if necessary, the * appropriate activity. */ - private class CreateShortcut implements ExpandableListView.OnChildClickListener, - DialogInterface.OnCancelListener, ExpandableListView.OnGroupExpandListener { + private class CreateShortcut implements AdapterView.OnItemClickListener, + DialogInterface.OnCancelListener { private AddAdapter mAdapter; - private ExpandableListView mList; - + private ListView mList; + Dialog createDialog() { mWaitingForResult = true; - mAdapter = new AddAdapter(Launcher.this, false); + + mAdapter = new AddAdapter(Launcher.this); final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this); builder.setTitle(getString(R.string.menu_item_add_item)); builder.setIcon(0); - mList = (ExpandableListView) + mList = (ListView) View.inflate(Launcher.this, R.layout.create_shortcut_list, null); mList.setAdapter(mAdapter); - mList.setOnChildClickListener(this); - mList.setOnGroupExpandListener(this); + mList.setOnItemClickListener(this); builder.setView(mList); builder.setInverseBackgroundForced(true); @@ -1539,13 +1654,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On return dialog; } - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, - int childPosition, long id) { - mAdapter.performAction(groupPosition, childPosition); - cleanup(); - return true; - } - public void onCancel(DialogInterface dialog) { mWaitingForResult = false; cleanup(); @@ -1556,10 +1664,75 @@ public final class Launcher extends Activity implements View.OnClickListener, On dismissDialog(DIALOG_CREATE_SHORTCUT); } - public void onGroupExpand(int groupPosition) { - long packged = ExpandableListView.getPackedPositionForGroup(groupPosition); - int position = mList.getFlatListPosition(packged); - mList.setSelectionFromTop(position, 0); + public void onItemClick(AdapterView parent, View view, int position, long id) { + // handle which item was clicked based on position + // this will launch off pick intent + + Object tag = view.getTag(); + if (tag instanceof AddAdapter.ListItem) { + AddAdapter.ListItem item = (AddAdapter.ListItem) tag; + cleanup(); + switch (item.actionTag) { + case AddAdapter.ITEM_APPLICATION: { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); + startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION); + break; + } + + case AddAdapter.ITEM_SHORTCUT: { + Intent shortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, shortcutIntent); + pickIntent.putExtra(Intent.EXTRA_TITLE, + getText(R.string.title_select_shortcut)); + startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT); + break; + } + + case AddAdapter.ITEM_SEARCH: { + addSearch(); + break; + } + + case AddAdapter.ITEM_APPWIDGET: { + int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId(); + + Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); + pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET); + break; + } + + case AddAdapter.ITEM_LIVE_FOLDER: { + Intent liveFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, liveFolderIntent); + pickIntent.putExtra(Intent.EXTRA_TITLE, + getText(R.string.title_select_live_folder)); + startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER); + break; + } + + case AddAdapter.ITEM_FOLDER: { + addFolder(!mDesktopLocked); + dismissDialog(DIALOG_CREATE_SHORTCUT); + break; + } + + case AddAdapter.ITEM_WALLPAPER: { + startWallpaper(); + break; + } + + } + + } } } @@ -1569,13 +1742,24 @@ public final class Launcher extends Activity implements View.OnClickListener, On private class ApplicationsIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - //noinspection ConstantConditions - if (REMOVE_SHORTCUT_ON_PACKAGE_REMOVE && - Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { - removeShortcutsForPackage(intent.getData().getSchemeSpecificPart()); + boolean reloadWorkspace = false; +android.util.Log.d("Home", "application intent received: " + intent.getAction()); +android.util.Log.d("Home", " --> " + intent.getData()); + if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + removeShortcutsForPackage(intent.getData().getSchemeSpecificPart()); + } else { + reloadWorkspace = true; + } } removeDialog(DIALOG_CREATE_SHORTCUT); - sModel.loadApplications(false, Launcher.this, false); + if (!reloadWorkspace) { +android.util.Log.d("Home", " --> loading apps"); + sModel.loadApplications(false, Launcher.this, false); + } else { +android.util.Log.d("Home", " --> loading workspace"); + sModel.loadUserItems(false, Launcher.this, false, true); + } } } @@ -1637,48 +1821,93 @@ public final class Launcher extends Activity implements View.OnClickListener, On public void onDrawerOpened() { if (!mOpen) { mHandleIcon.reverseTransition(150); + + final Rect bounds = mWorkspace.mDrawerBounds; + offsetBoundsToDragLayer(bounds, mAllAppsGrid); + mOpen = true; } } + private void offsetBoundsToDragLayer(Rect bounds, View view) { + view.getDrawingRect(bounds); + + while (view != mDragLayer) { + bounds.offset(view.getLeft(), view.getTop()); + view = (View) view.getParent(); + } + } + public void onDrawerClosed() { if (mOpen) { mHandleIcon.reverseTransition(150); + mWorkspace.mDrawerBounds.setEmpty(); mOpen = false; } + mAllAppsGrid.setSelection(0); mAllAppsGrid.clearTextFilter(); - mWorkspace.clearChildrenCache(); } public void onScrollStarted() { - mWorkspace.enableChildrenCache(); + if (PROFILE_DRAWER) { + android.os.Debug.startMethodTracing("/sdcard/launcher-drawer"); + } + + mWorkspace.mDrawerContentWidth = mAllAppsGrid.getWidth(); + mWorkspace.mDrawerContentHeight = mAllAppsGrid.getHeight(); } public void onScrollEnded() { + if (PROFILE_DRAWER) { + android.os.Debug.stopMethodTracing(); + } } } - private static class DesktopItemsBinder extends Handler { + private static class DesktopBinder extends Handler { static final int MESSAGE_BIND_ITEMS = 0x1; + static final int MESSAGE_BIND_APPWIDGETS = 0x2; // Number of items to bind in every pass static final int ITEMS_COUNT = 6; + static final int APPWIDGETS_COUNT = 1; private final ArrayList mShortcuts; + private final ArrayList mAppWidgets; private final WeakReference mLauncher; - DesktopItemsBinder(Launcher launcher, ArrayList shortcuts) { + DesktopBinder(Launcher launcher, ArrayList shortcuts, + ArrayList appWidgets) { + mLauncher = new WeakReference(launcher); mShortcuts = shortcuts; + mAppWidgets = appWidgets; + } + + public void startBindingItems() { + obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget(); } + public void startBindingAppWidgets() { + obtainMessage(MESSAGE_BIND_APPWIDGETS, 0, mAppWidgets.size()).sendToTarget(); + } + @Override public void handleMessage(Message msg) { + Launcher launcher = mLauncher.get(); + if (launcher == null) { + return; + } + switch (msg.what) { - case MESSAGE_BIND_ITEMS: - Launcher launcher = mLauncher.get(); - if (launcher != null) launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2); + case MESSAGE_BIND_ITEMS: { + launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2); break; + } + case MESSAGE_BIND_APPWIDGETS: { + launcher.bindAppWidgets(this, mAppWidgets, msg.arg1, msg.arg2); + break; + } } } } diff --git a/src/com/android/launcher/LauncherAppWidgetHost.java b/src/com/android/launcher/LauncherAppWidgetHost.java new file mode 100644 index 0000000000..22fd5b6f30 --- /dev/null +++ b/src/com/android/launcher/LauncherAppWidgetHost.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher; + +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; + +/** + * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} + * which correctly captures all long-press events. This ensures that users can + * always pick up and move widgets. + */ +public class LauncherAppWidgetHost extends AppWidgetHost { + public LauncherAppWidgetHost(Context context, int hostId) { + super(context, hostId); + } + + @Override + protected AppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + return new LauncherAppWidgetHostView(context); + } +} diff --git a/src/com/android/launcher/LauncherAppWidgetHostView.java b/src/com/android/launcher/LauncherAppWidgetHostView.java new file mode 100644 index 0000000000..1e21a19933 --- /dev/null +++ b/src/com/android/launcher/LauncherAppWidgetHostView.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher; + +import android.appwidget.AppWidgetHostView; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +/** + * {@inheritDoc} + */ +public class LauncherAppWidgetHostView extends AppWidgetHostView { + private boolean mHasPerformedLongPress; + + private CheckForLongPress mPendingCheckForLongPress; + + private LayoutInflater mInflater; + + public LauncherAppWidgetHostView(Context context) { + super(context); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + protected View getErrorView() { + return mInflater.inflate(R.layout.appwidget_error, this, false); + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + // Consume any touch events for ourselves after longpress is triggered + if (mHasPerformedLongPress) { + mHasPerformedLongPress = false; + return true; + } + + // Watch for longpress events at this level to make sure + // users can always pick up this widget + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + postCheckForLongClick(); + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mHasPerformedLongPress = false; + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + break; + } + + // Otherwise continue letting touch events fall through to children + return false; + } + + class CheckForLongPress implements Runnable { + private int mOriginalWindowAttachCount; + + public void run() { + if ((mParent != null) && hasWindowFocus() + && mOriginalWindowAttachCount == getWindowAttachCount() + && !mHasPerformedLongPress) { + if (performLongClick()) { + mHasPerformedLongPress = true; + } + } + } + + public void rememberWindowAttachCount() { + mOriginalWindowAttachCount = getWindowAttachCount(); + } + } + + private void postCheckForLongClick() { + mHasPerformedLongPress = false; + + if (mPendingCheckForLongPress == null) { + mPendingCheckForLongPress = new CheckForLongPress(); + } + mPendingCheckForLongPress.rememberWindowAttachCount(); + postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout()); + } +} diff --git a/src/com/android/launcher/LauncherAppWidgetInfo.java b/src/com/android/launcher/LauncherAppWidgetInfo.java new file mode 100644 index 0000000000..3b5f08edf1 --- /dev/null +++ b/src/com/android/launcher/LauncherAppWidgetInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher; + +import android.appwidget.AppWidgetHostView; +import android.content.ContentValues; + +/** + * Represents a widget, which just contains an identifier. + */ +class LauncherAppWidgetInfo extends ItemInfo { + + /** + * Identifier for this widget when talking with {@link AppWidgetManager} for updates. + */ + int appWidgetId; + + /** + * View that holds this widget after it's been created. This view isn't created + * until Launcher knows it's needed. + */ + AppWidgetHostView hostView = null; + + LauncherAppWidgetInfo(int appWidgetId) { + itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; + this.appWidgetId = appWidgetId; + } + + @Override + void onAddToDatabase(ContentValues values) { + super.onAddToDatabase(values); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); + } + + @Override + public String toString() { + return Integer.toString(appWidgetId); + } +} diff --git a/src/com/android/launcher/LauncherModel.java b/src/com/android/launcher/LauncherModel.java index 314a502d4e..70b4c10f4c 100644 --- a/src/com/android/launcher/LauncherModel.java +++ b/src/com/android/launcher/LauncherModel.java @@ -33,7 +33,6 @@ import android.util.Log; import android.os.Process; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Comparator; @@ -44,33 +43,41 @@ import java.net.URISyntaxException; /** * Maintains in-memory state of the Launcher. It is expected that there should be only one * LauncherModel object held in a static. Also provide APIs for updating the database state - * for the Launcher + * for the Launcher. */ public class LauncherModel { private static final int UI_NOTIFICATION_RATE = 4; private static final int DEFAULT_APPLICATIONS_NUMBER = 42; private static final long APPLICATION_NOT_RESPONDING_TIMEOUT = 5000; + private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - private final Collator sCollator = Collator.getInstance(); + private static final boolean DEBUG = false; + + private static final Collator sCollator = Collator.getInstance(); private boolean mApplicationsLoaded; private boolean mDesktopItemsLoaded; private ArrayList mDesktopItems; + private ArrayList mDesktopAppWidgets; private HashMap mFolders; private ArrayList mApplications; private ApplicationsAdapter mApplicationsAdapter; private ApplicationsLoader mApplicationsLoader; private DesktopItemsLoader mDesktopItemsLoader; - private Thread mLoader; - private Thread mDesktopLoader; + private Thread mApplicationsLoaderThread; + private Thread mDesktopLoaderThread; - void abortLoaders() { + private final HashMap mAppInfoCache = + new HashMap(INITIAL_ICON_CACHE_CAPACITY); + + synchronized void abortLoaders() { if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { mApplicationsLoader.stop(); mApplicationsLoaded = false; } + if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) { mDesktopItemsLoader.stop(); mDesktopItemsLoaded = false; @@ -78,41 +85,72 @@ public class LauncherModel { } /** - * Loads the list of installed applications in mApplications. + * Drop our cache of components to their lables & icons. We do + * this from Launcher when applications are added/removed. It's a + * bit overkill, but it's a rare operation anyway. */ - void loadApplications(boolean isLaunching, Launcher launcher, boolean localeChanged) { + synchronized void dropApplicationCache() { + mAppInfoCache.clear(); + } + + /** + * Loads the list of installed applications in mApplications. + * + * @return true if the applications loader must be started + * (see startApplicationsLoader()), false otherwise. + */ + synchronized boolean loadApplications(boolean isLaunching, Launcher launcher, + boolean localeChanged) { +android.util.Log.d("Home", "load applications"); if (isLaunching && mApplicationsLoaded && !localeChanged) { mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications); - return; +android.util.Log.d("Home", " --> applications loaded, return"); + return false; } + waitForApplicationsLoader(); + + if (localeChanged) { + dropApplicationCache(); + } + if (mApplicationsAdapter == null || isLaunching || localeChanged) { - mApplicationsAdapter = new ApplicationsAdapter(launcher, - mApplications = new ArrayList(DEFAULT_APPLICATIONS_NUMBER)); - } - - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - mApplicationsLoader.stop(); - // Wait for the currently running thread to finish, this can take a little - // time but it should be well below the timeout limit - try { - mLoader.join(APPLICATION_NOT_RESPONDING_TIMEOUT); - } catch (InterruptedException e) { - // Empty - } + mApplications = new ArrayList(DEFAULT_APPLICATIONS_NUMBER); + mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications); } mApplicationsLoaded = false; if (!isLaunching) { startApplicationsLoader(launcher); + return false; + } + + return true; + } + + private synchronized void waitForApplicationsLoader() { + if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { + android.util.Log.d("Home", " --> wait for applications loader"); + + mApplicationsLoader.stop(); + // Wait for the currently running thread to finish, this can take a little + // time but it should be well below the timeout limit + try { + mApplicationsLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT); + } catch (InterruptedException e) { + // EMpty + } } } - private void startApplicationsLoader(Launcher launcher) { + private synchronized void startApplicationsLoader(Launcher launcher) { +android.util.Log.d("Home", " --> starting applications loader"); + waitForApplicationsLoader(); + mApplicationsLoader = new ApplicationsLoader(launcher); - mLoader = new Thread(mApplicationsLoader, "Applications Loader"); - mLoader.start(); + mApplicationsLoaderThread = new Thread(mApplicationsLoader, "Applications Loader"); + mApplicationsLoaderThread.start(); } private class ApplicationsLoader implements Runnable { @@ -149,36 +187,39 @@ public class LauncherModel { final int count = apps.size(); final ApplicationsAdapter applicationList = mApplicationsAdapter; - ChangeNotifier action = new ChangeNotifier(applicationList); + ChangeNotifier action = new ChangeNotifier(applicationList, true); + final HashMap appInfoCache = mAppInfoCache; for (int i = 0; i < count && !mStopped; i++) { - ApplicationInfo application = new ApplicationInfo(); ResolveInfo info = apps.get(i); - - application.title = info.loadLabel(manager); - if (application.title == null) { - application.title = info.activityInfo.name; - } - application.setActivity(new ComponentName( + ComponentName componentName = new ComponentName( info.activityInfo.applicationInfo.packageName, - info.activityInfo.name), - Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - application.icon = info.activityInfo.loadIcon(manager); - application.container = ItemInfo.NO_ID; - - action.add(application); - } - - action.sort(new Comparator() { - public final int compare(ApplicationInfo a, ApplicationInfo b) { - return sCollator.compare(a.title.toString(), b.title.toString()); + info.activityInfo.name); + ApplicationInfo application = appInfoCache.get(componentName); + if (application == null) { + application = new ApplicationInfo(); + application.title = info.loadLabel(manager); + if (application.title == null) { + application.title = info.activityInfo.name; + } + application.setActivity(componentName, + Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + application.container = ItemInfo.NO_ID; + application.icon = info.activityInfo.loadIcon(manager); + if (DEBUG) { + Log.d(Launcher.LOG_TAG, "Loaded ApplicationInfo for " + componentName); + } + appInfoCache.put(componentName, application); } - }); - if (!mStopped) { - launcher.runOnUiThread(action); + if (action.add(application)) { + launcher.runOnUiThread(action); + action = new ChangeNotifier(applicationList, false); + } } + + launcher.runOnUiThread(action); } if (!mStopped) { @@ -188,51 +229,66 @@ public class LauncherModel { } } - private static class ChangeNotifier implements Runnable { + private static class ChangeNotifier implements Runnable, Comparator { private final ApplicationsAdapter mApplicationList; - private ArrayList mBuffer; + private final ArrayList mBuffer; - ChangeNotifier(ApplicationsAdapter applicationList) { + private boolean mFirst = true; + + ChangeNotifier(ApplicationsAdapter applicationList, boolean first) { mApplicationList = applicationList; + mFirst = first; mBuffer = new ArrayList(UI_NOTIFICATION_RATE); } public void run() { - final ArrayList buffer = mBuffer; final ApplicationsAdapter applicationList = mApplicationList; + + if (mFirst) { + applicationList.setNotifyOnChange(false); + applicationList.clear(); + mFirst = false; + } + + final ArrayList buffer = mBuffer; final int count = buffer.size(); - applicationList.clear(); for (int i = 0; i < count; i++) { applicationList.setNotifyOnChange(false); applicationList.add(buffer.get(i)); } - applicationList.notifyDataSetChanged(); buffer.clear(); + + applicationList.sort(this); + applicationList.notifyDataSetChanged(); } - void add(ApplicationInfo application) { - mBuffer.add(application); + boolean add(ApplicationInfo application) { + final ArrayList buffer = mBuffer; + buffer.add(application); + return buffer.size() >= UI_NOTIFICATION_RATE; } - void sort(Comparator comparator) { - Collections.sort(mBuffer, comparator); + public final int compare(ApplicationInfo a, ApplicationInfo b) { + return sCollator.compare(a.title.toString(), b.title.toString()); } } boolean isDesktopLoaded() { - return mDesktopItems != null && mDesktopItemsLoaded; + return mDesktopItems != null && mDesktopAppWidgets != null && mDesktopItemsLoaded; } - + /** * Loads all of the items on the desktop, in folders, or in the dock. * These can be apps, shortcuts or widgets */ void loadUserItems(boolean isLaunching, Launcher launcher, boolean localeChanged, boolean loadApplications) { +android.util.Log.d("Home", "loading user items"); - if (isLaunching && mDesktopItems != null && mDesktopItemsLoaded) { + if (isLaunching && isDesktopLoaded()) { +android.util.Log.d("Home", " --> items loaded, return"); if (loadApplications) startApplicationsLoader(launcher); // We have already loaded our data from the DB launcher.onDesktopItemsLoaded(); @@ -244,16 +300,17 @@ public class LauncherModel { // Wait for the currently running thread to finish, this can take a little // time but it should be well below the timeout limit try { - mDesktopLoader.join(APPLICATION_NOT_RESPONDING_TIMEOUT); + mDesktopLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT); } catch (InterruptedException e) { // Empty } } +android.util.Log.d("Home", " --> starting workspace loader"); mDesktopItemsLoaded = false; mDesktopItemsLoader = new DesktopItemsLoader(launcher, localeChanged, loadApplications); - mDesktopLoader = new Thread(mDesktopItemsLoader, "Desktop Items Loader"); - mDesktopLoader.start(); + mDesktopLoaderThread = new Thread(mDesktopItemsLoader, "Desktop Items Loader"); + mDesktopLoaderThread.start(); } private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) { @@ -272,7 +329,8 @@ public class LauncherModel { try { while (c.moveToNext()) { try { - if (c.getInt(itemTypeIndex) != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + if (c.getInt(itemTypeIndex) != + LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { continue; } @@ -357,9 +415,11 @@ public class LauncherModel { } mDesktopItems = new ArrayList(); + mDesktopAppWidgets = new ArrayList(); mFolders = new HashMap(); final ArrayList desktopItems = mDesktopItems; + final ArrayList desktopAppWidgets = mDesktopAppWidgets; final Cursor c = contentResolver.query( LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); @@ -374,15 +434,19 @@ public class LauncherModel { final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID); final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); + final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); + final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); ApplicationInfo info; String intentDescription; - Widget widgetInfo = null; + Widget widgetInfo; + LauncherAppWidgetInfo appWidgetInfo; int container; long id; Intent intent; @@ -494,41 +558,44 @@ public class LauncherModel { break; } break; - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_CLOCK: case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME: - switch (itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_CLOCK: - widgetInfo = Widget.makeClock(); - break; - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: - widgetInfo = Widget.makeSearch(); - break; - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME: - widgetInfo = Widget.makePhotoFrame(); - byte[] data = c.getBlob(iconIndex); - if (data != null) { - widgetInfo.photo = - BitmapFactory.decodeByteArray(data, 0, data.length); - } - break; + widgetInfo = Widget.makeSearch(); + + container = c.getInt(containerIndex); + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { + Log.e(Launcher.LOG_TAG, "Widget found where container " + + "!= CONTAINER_DESKTOP ignoring!"); + continue; } - if (widgetInfo != null) { - container = c.getInt(containerIndex); - if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { - Log.e(Launcher.LOG_TAG, "Widget found where container " - + "!= CONTAINER_DESKTOP -- ignoring!"); - continue; - } - widgetInfo.id = c.getLong(idIndex); - widgetInfo.screen = c.getInt(screenIndex); - widgetInfo.container = container; - widgetInfo.cellX = c.getInt(cellXIndex); - widgetInfo.cellY = c.getInt(cellYIndex); + widgetInfo.id = c.getLong(idIndex); + widgetInfo.screen = c.getInt(screenIndex); + widgetInfo.container = container; + widgetInfo.cellX = c.getInt(cellXIndex); + widgetInfo.cellY = c.getInt(cellYIndex); - desktopItems.add(widgetInfo); + desktopItems.add(widgetInfo); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + // Read all Launcher-specific widget details + int appWidgetId = c.getInt(appWidgetIdIndex); + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); + appWidgetInfo.id = c.getLong(idIndex); + appWidgetInfo.screen = c.getInt(screenIndex); + appWidgetInfo.cellX = c.getInt(cellXIndex); + appWidgetInfo.cellY = c.getInt(cellYIndex); + appWidgetInfo.spanX = c.getInt(spanXIndex); + appWidgetInfo.spanY = c.getInt(spanYIndex); + + container = c.getInt(containerIndex); + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { + Log.e(Launcher.LOG_TAG, "Widget found where container " + + "!= CONTAINER_DESKTOP -- ignoring!"); + continue; } + appWidgetInfo.container = c.getInt(containerIndex); + + desktopAppWidgets.add(appWidgetInfo); break; } } catch (Exception e) { @@ -578,7 +645,7 @@ public class LauncherModel { break; default: liveFolderInfo.icon = - launcher.getResources().getDrawable(R.drawable.ic_launcher_folder); + launcher.getResources().getDrawable(R.drawable.ic_launcher_folder); } } @@ -586,7 +653,7 @@ public class LauncherModel { * Finds the user folder defined by the specified id. * * @param id The id of the folder to look for. - * + * * @return A UserFolderInfo if the folder exists or null otherwise. */ FolderInfo findFolderById(long id) { @@ -635,8 +702,10 @@ public class LauncherModel { mApplicationsAdapter = null; unbindAppDrawables(mApplications); unbindDrawables(mDesktopItems); + unbindAppWidgetHostViews(mDesktopAppWidgets); + unbindCachedIconDrawables(); } - + /** * Remove the callback for the cached drawables or we leak the previous * Home screen on orientation change. @@ -650,11 +719,12 @@ public class LauncherModel { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: ((ApplicationInfo)item).icon.setCallback(null); + break; } } } } - + /** * Remove the callback for the cached drawables or we leak the previous * Home screen on orientation change. @@ -669,31 +739,54 @@ public class LauncherModel { } /** - * @return The current list of applications + * Remove any {@link LauncherAppWidgetHostView} references in our widgets. */ - public ArrayList getApplications() { - return mApplications; + private void unbindAppWidgetHostViews(ArrayList appWidgets) { + if (appWidgets != null) { + final int count = appWidgets.size(); + for (int i = 0; i < count; i++) { + LauncherAppWidgetInfo launcherInfo = appWidgets.get(i); + launcherInfo.hostView = null; + } + } + } + + /** + * Remove the callback for the cached drawables or we leak the previous + * Home screen on orientation change. + */ + private void unbindCachedIconDrawables() { + for (ApplicationInfo appInfo : mAppInfoCache.values()) { + appInfo.icon.setCallback(null); + } } /** * @return The current list of applications */ - public ApplicationsAdapter getApplicationsAdapter() { + ApplicationsAdapter getApplicationsAdapter() { return mApplicationsAdapter; } /** * @return The current list of desktop items */ - public ArrayList getDesktopItems() { + ArrayList getDesktopItems() { return mDesktopItems; } + + /** + * @return The current list of desktop items + */ + ArrayList getDesktopAppWidgets() { + return mDesktopAppWidgets; + } /** * Add an item to the desktop * @param info */ - public void addDesktopItem(ItemInfo info) { + void addDesktopItem(ItemInfo info) { // TODO: write to DB; also check that folder has been added to folders list mDesktopItems.add(info); } @@ -702,11 +795,25 @@ public class LauncherModel { * Remove an item from the desktop * @param info */ - public void removeDesktopItem(ItemInfo info) { + void removeDesktopItem(ItemInfo info) { // TODO: write to DB; figure out if we should remove folder from folders list mDesktopItems.remove(info); } + /** + * Add a widget to the desktop + */ + void addDesktopAppWidget(LauncherAppWidgetInfo info) { + mDesktopAppWidgets.add(info); + } + + /** + * Remove a widget from the desktop + */ + void removeDesktopAppWidget(LauncherAppWidgetInfo info) { + mDesktopAppWidgets.remove(info); + } + /** * Make an ApplicationInfo object for an application */ @@ -886,37 +993,6 @@ public class LauncherModel { return null; } - static Widget getPhotoFrameInfo(Context context, int screen, int cellX, int cellY) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - null, "screen=? and cellX=? and cellY=? and itemType=?", - new String[] { String.valueOf(screen), String.valueOf(cellX), String.valueOf(cellY), - String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) }, null); - - try { - if (c.moveToFirst()) { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID); - final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); - final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); - final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - - Widget widgetInfo = Widget.makePhotoFrame(); - widgetInfo.id = c.getLong(idIndex); - widgetInfo.screen = c.getInt(screenIndex); - widgetInfo.container = c.getInt(containerIndex); - widgetInfo.cellX = c.getInt(cellXIndex); - widgetInfo.cellY = c.getInt(cellYIndex); - - return widgetInfo; - } - } finally { - c.close(); - } - - return null; - } - /** * Add an item to the database in a specified container. Sets the container, screen, cellX and * cellY fields of the item. Also assigns an ID to the item. @@ -972,7 +1048,7 @@ public class LauncherModel { final ContentResolver cr = context.getContentResolver(); cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); - cr.delete(LauncherSettings.Favorites.CONTENT_URI, LauncherSettings.Favorites.CONTAINER + "=" + info.id, - null); + cr.delete(LauncherSettings.Favorites.CONTENT_URI, + LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); } } diff --git a/src/com/android/launcher/LauncherProvider.java b/src/com/android/launcher/LauncherProvider.java index a3e529d328..5cd7a0ff98 100644 --- a/src/com/android/launcher/LauncherProvider.java +++ b/src/com/android/launcher/LauncherProvider.java @@ -16,6 +16,7 @@ package com.android.launcher; +import android.appwidget.AppWidgetHost; import android.content.ContentProvider; import android.content.Context; import android.content.ContentValues; @@ -29,6 +30,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.database.Cursor; +import android.database.SQLException; import android.util.Log; import android.util.Xml; import android.net.Uri; @@ -40,18 +42,25 @@ import java.io.FileReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import com.android.internal.util.XmlUtils; +import com.android.launcher.LauncherSettings.Favorites; public class LauncherProvider extends ContentProvider { - private static final String LOG_TAG = "LauncherSettingsProvider"; + private static final String LOG_TAG = "LauncherProvider"; + private static final boolean LOGD = true; private static final String DATABASE_NAME = "launcher.db"; - private static final int DATABASE_VERSION = 1; + + private static final int DATABASE_VERSION = 3; static final String AUTHORITY = "com.android.launcher.settings"; + + static final String EXTRA_BIND_SOURCES = "com.android.launcher.settings.bindsources"; + static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets"; static final String TABLE_FAVORITES = "favorites"; static final String PARAMETER_NOTIFY = "notify"; @@ -168,14 +177,18 @@ public class LauncherProvider extends ContentProvider { private static final String ATTRIBUTE_Y = "y"; private final Context mContext; + private final AppWidgetHost mAppWidgetHost; DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); mContext = context; + mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); } @Override public void onCreate(SQLiteDatabase db) { + if (LOGD) Log.d(LOG_TAG, "creating new launcher database"); + db.execSQL("CREATE TABLE favorites (" + "_id INTEGER PRIMARY KEY," + "title TEXT," + @@ -187,6 +200,7 @@ public class LauncherProvider extends ContentProvider { "spanX INTEGER," + "spanY INTEGER," + "itemType INTEGER," + + "appWidgetId INTEGER NOT NULL DEFAULT -1," + "isShortcut INTEGER," + "iconType INTEGER," + "iconPackage TEXT," + @@ -196,6 +210,11 @@ public class LauncherProvider extends ContentProvider { "displayMode INTEGER" + ");"); + // Database was just created, so wipe any previous widgets + if (mAppWidgetHost != null) { + mAppWidgetHost.deleteHost(); + } + if (!convertDatabase(db)) { // Populate favorites table with initial favorites loadFavorites(db, DEFAULT_FAVORITES_PATH); @@ -203,10 +222,11 @@ public class LauncherProvider extends ContentProvider { } private boolean convertDatabase(SQLiteDatabase db) { + if (LOGD) Log.d(LOG_TAG, "converting database from an older format, but not onUpgrade"); boolean converted = false; final Uri uri = Uri.parse("content://" + Settings.AUTHORITY + - "/favorites?notify=true"); + "/old_favorites?notify=true"); final ContentResolver resolver = mContext.getContentResolver(); Cursor cursor = null; @@ -228,6 +248,12 @@ public class LauncherProvider extends ContentProvider { resolver.delete(uri, null, null); } } + + if (converted) { + // Convert widgets from this import into widgets + if (LOGD) Log.d(LOG_TAG, "converted and now triggering widget upgrade"); + convertWidgets(db); + } return converted; } @@ -261,6 +287,7 @@ public class LauncherProvider extends ContentProvider { values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); @@ -290,14 +317,130 @@ public class LauncherProvider extends ContentProvider { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + - newVersion + ", which will destroy all old data"); + if (LOGD) Log.d(LOG_TAG, "onUpgrade triggered"); + + int version = oldVersion; + if (version < 3) { + // upgrade 1,2 -> 3 added appWidgetId column + db.beginTransaction(); + try { + // Insert new column for holding appWidgetIds + db.execSQL("ALTER TABLE favorites " + + "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); + db.setTransactionSuccessful(); + version = 3; + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(LOG_TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + // Convert existing widgets only if table upgrade was successful + if (version == 3) { + convertWidgets(db); + } + } + + if (version != DATABASE_VERSION) { + Log.w(LOG_TAG, "Destroying all old data."); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); + onCreate(db); + } + } + + /** + * Upgrade existing clock and photo frame widgets into their new widget + * equivalents. This method allocates appWidgetIds, and then hands off to + * LauncherAppWidgetBinder to finish the actual binding. + */ + private void convertWidgets(SQLiteDatabase db) { + final int[] bindSources = new int[] { + Favorites.ITEM_TYPE_WIDGET_CLOCK, + Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, + }; + + final ArrayList bindTargets = new ArrayList(); + bindTargets.add(new ComponentName("com.android.alarmclock", + "com.android.alarmclock.AnalogAppWidgetProvider")); + bindTargets.add(new ComponentName("com.android.camera", + "com.android.camera.PhotoAppWidgetProvider")); + + final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); + + Cursor c = null; + boolean allocatedAppWidgets = false; + + db.beginTransaction(); + try { + // Select and iterate through each matching widget + c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID }, + selectWhere, null, null, null, null); + + if (LOGD) Log.d(LOG_TAG, "found upgrade cursor count="+c.getCount()); + + final ContentValues values = new ContentValues(); + while (c != null && c.moveToNext()) { + long favoriteId = c.getLong(0); + + // Allocate and update database with new appWidgetId + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + if (LOGD) Log.d(LOG_TAG, "allocated appWidgetId="+appWidgetId+" for favoriteId="+favoriteId); + + values.clear(); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); + + // Original widgets might not have valid spans when upgrading + values.put(LauncherSettings.Favorites.SPANX, 2); + values.put(LauncherSettings.Favorites.SPANY, 2); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); - onCreate(db); + String updateWhere = Favorites._ID + "=" + favoriteId; + db.update(TABLE_FAVORITES, values, updateWhere, null); + + allocatedAppWidgets = true; + } catch (RuntimeException ex) { + Log.e(LOG_TAG, "Problem allocating appWidgetId", ex); + } + } + + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(LOG_TAG, "Problem while allocating appWidgetIds for existing widgets", ex); + } finally { + db.endTransaction(); + if (c != null) { + c.close(); + } + } + + // If any appWidgetIds allocated, then launch over to binder + if (allocatedAppWidgets) { + launchAppWidgetBinder(bindSources, bindTargets); + } } - + /** + * Launch the widget binder that walks through the Launcher database, + * binding any matching widgets to the corresponding targets. We can't + * bind ourselves because our parent process can't obtain the + * BIND_APPWIDGET permission. + */ + private void launchAppWidgetBinder(int[] bindSources, ArrayList bindTargets) { + final Intent intent = new Intent(); + intent.setComponent(new ComponentName("com.android.settings", + "com.android.settings.LauncherAppWidgetBinder")); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + final Bundle extras = new Bundle(); + extras.putIntArray(EXTRA_BIND_SOURCES, bindSources); + extras.putParcelableArrayList(EXTRA_BIND_TARGETS, bindTargets); + intent.putExtras(extras); + + mContext.startActivity(intent); + } + /** * Loads the default set of favorite packages from an xml file. * @@ -370,20 +513,7 @@ public class LauncherProvider extends ContentProvider { } catch (IOException e) { Log.w(LOG_TAG, "Got exception parsing favorites.", e); } - - // Add a clock - values.clear(); - values.put(LauncherSettings.Favorites.CONTAINER, - LauncherSettings.Favorites.CONTAINER_DESKTOP); - values.put(LauncherSettings.Favorites.ITEM_TYPE, - LauncherSettings.Favorites.ITEM_TYPE_WIDGET_CLOCK); - values.put(LauncherSettings.Favorites.SCREEN, 1); - values.put(LauncherSettings.Favorites.CELLX, 1); - values.put(LauncherSettings.Favorites.CELLY, 0); - values.put(LauncherSettings.Favorites.SPANX, 2); - values.put(LauncherSettings.Favorites.SPANY, 2); - db.insert(TABLE_FAVORITES, null, values); - + // Add a search box values.clear(); values.put(LauncherSettings.Favorites.CONTAINER, @@ -396,11 +526,63 @@ public class LauncherProvider extends ContentProvider { values.put(LauncherSettings.Favorites.SPANX, 4); values.put(LauncherSettings.Favorites.SPANY, 1); db.insert(TABLE_FAVORITES, null, values); + + final int[] bindSources = new int[] { + Favorites.ITEM_TYPE_WIDGET_CLOCK, + }; + + final ArrayList bindTargets = new ArrayList(); + bindTargets.add(new ComponentName("com.android.alarmclock", + "com.android.alarmclock.AnalogAppWidgetProvider")); + + boolean allocatedAppWidgets = false; + + // Try binding to an analog clock widget + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + values.clear(); + values.put(LauncherSettings.Favorites.CONTAINER, + LauncherSettings.Favorites.CONTAINER_DESKTOP); + values.put(LauncherSettings.Favorites.ITEM_TYPE, + LauncherSettings.Favorites.ITEM_TYPE_WIDGET_CLOCK); + values.put(LauncherSettings.Favorites.SCREEN, 1); + values.put(LauncherSettings.Favorites.CELLX, 1); + values.put(LauncherSettings.Favorites.CELLY, 0); + values.put(LauncherSettings.Favorites.SPANX, 2); + values.put(LauncherSettings.Favorites.SPANY, 2); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); + db.insert(TABLE_FAVORITES, null, values); + + allocatedAppWidgets = true; + } catch (RuntimeException ex) { + Log.e(LOG_TAG, "Problem allocating appWidgetId", ex); + } + // If any appWidgetIds allocated, then launch over to binder + if (allocatedAppWidgets) { + launchAppWidgetBinder(bindSources, bindTargets); + } + return i; } } + /** + * Build a query string that will match any row where the column matches + * anything in the values list. + */ + static String buildOrWhereString(String column, int[] values) { + StringBuilder selectWhere = new StringBuilder(); + for (int i = values.length - 1; i >= 0; i--) { + selectWhere.append(column).append("=").append(values[i]); + if (i > 0) { + selectWhere.append(" OR "); + } + } + return selectWhere.toString(); + } + static class SqlArguments { public final String table; public final String where; diff --git a/src/com/android/launcher/LauncherSettings.java b/src/com/android/launcher/LauncherSettings.java index c5dfd1ff02..60ea0df6ae 100644 --- a/src/com/android/launcher/LauncherSettings.java +++ b/src/com/android/launcher/LauncherSettings.java @@ -24,7 +24,8 @@ import android.net.Uri; */ class LauncherSettings { /** - * Favorites. + * Favorites. When changing these values, be sure to update + * {@link com.android.settings.LauncherAppWidgetBinder} as needed. */ static final class Favorites implements BaseColumns { /** @@ -146,6 +147,11 @@ class LauncherSettings { */ static final int ITEM_TYPE_LIVE_FOLDER = 3; + /** + * The favorite is a widget + */ + static final int ITEM_TYPE_APPWIDGET = 4; + /** * The favorite is a clock */ @@ -161,6 +167,13 @@ class LauncherSettings { */ static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002; + /** + * The appWidgetId of the widget + * + *

Type: INTEGER

+ */ + static final String APPWIDGET_ID = "appWidgetId"; + /** * Indicates whether this favorite is an application-created shortcut or not. * If the value is 0, the favorite is not an application-created shortcut, if the diff --git a/src/com/android/launcher/LiveFolder.java b/src/com/android/launcher/LiveFolder.java index 37b98e0f7b..5d727f830d 100644 --- a/src/com/android/launcher/LiveFolder.java +++ b/src/com/android/launcher/LiveFolder.java @@ -24,8 +24,14 @@ import android.view.View; import android.widget.AdapterView; import android.net.Uri; import android.provider.LiveFolders; +import android.os.AsyncTask; +import android.database.Cursor; + +import java.lang.ref.WeakReference; public class LiveFolder extends Folder { + private AsyncTask mLoadingTask; + public LiveFolder(Context context, AttributeSet attrs) { super(context, attrs); } @@ -66,7 +72,10 @@ public class LiveFolder extends Folder { void bind(FolderInfo info) { super.bind(info); - setContentAdapter(new LiveFolderAdapter(mLauncher, (LiveFolderInfo) info)); + if (mLoadingTask != null && mLoadingTask.getStatus() == AsyncTask.Status.RUNNING) { + mLoadingTask.cancel(true); + } + mLoadingTask = new FolderLoadingTask(this).execute((LiveFolderInfo) info); } @Override @@ -78,6 +87,42 @@ public class LiveFolder extends Folder { @Override void onClose() { super.onClose(); + if (mLoadingTask != null && mLoadingTask.getStatus() == AsyncTask.Status.RUNNING) { + mLoadingTask.cancel(true); + } ((LiveFolderAdapter) mContent.getAdapter()).cleanup(); } + + static class FolderLoadingTask extends AsyncTask { + private final WeakReference mFolder; + private LiveFolderInfo mInfo; + + FolderLoadingTask(LiveFolder folder) { + mFolder = new WeakReference(folder); + } + + protected Cursor doInBackground(LiveFolderInfo... params) { + final LiveFolder folder = mFolder.get(); + if (folder != null) { + mInfo = params[0]; + return LiveFolderAdapter.query(folder.mLauncher, mInfo); + } + return null; + } + + @Override + protected void onPostExecute(Cursor cursor) { + if (!isCancelled()) { + if (cursor != null) { + final LiveFolder folder = mFolder.get(); + if (folder != null) { + final Launcher launcher = folder.mLauncher; + folder.setContentAdapter(new LiveFolderAdapter(launcher, mInfo, cursor)); + } + } + } else if (cursor != null) { + cursor.close(); + } + } + } } diff --git a/src/com/android/launcher/LiveFolderAdapter.java b/src/com/android/launcher/LiveFolderAdapter.java index 01db5a639b..4a5c077c89 100644 --- a/src/com/android/launcher/LiveFolderAdapter.java +++ b/src/com/android/launcher/LiveFolderAdapter.java @@ -45,8 +45,8 @@ class LiveFolderAdapter extends CursorAdapter { new HashMap>(); private final Launcher mLauncher; - LiveFolderAdapter(Launcher launcher, LiveFolderInfo info) { - super(launcher, query(launcher, info), true); + LiveFolderAdapter(Launcher launcher, LiveFolderInfo info, Cursor cursor) { + super(launcher, cursor, true); mIsList = info.displayMode == LiveFolders.DISPLAY_MODE_LIST; mInflater = LayoutInflater.from(launcher); mLauncher = launcher; @@ -54,8 +54,9 @@ class LiveFolderAdapter extends CursorAdapter { mLauncher.startManagingCursor(getCursor()); } - private static Cursor query(Context context, LiveFolderInfo info) { - return context.getContentResolver().query(info.uri, null, null, null, LiveFolders.NAME + " ASC"); + static Cursor query(Context context, LiveFolderInfo info) { + return context.getContentResolver().query(info.uri, null, null, + null, LiveFolders.NAME + " ASC"); } public View newView(Context context, Cursor cursor, ViewGroup parent) { @@ -178,10 +179,13 @@ class LiveFolderAdapter extends CursorAdapter { } mCustomIcons.clear(); - try { - getCursor().close(); - } finally { - mLauncher.stopManagingCursor(getCursor()); + final Cursor cursor = getCursor(); + if (cursor != null) { + try { + cursor.close(); + } finally { + mLauncher.stopManagingCursor(cursor); + } } } diff --git a/src/com/android/launcher/PhotoFrame.java b/src/com/android/launcher/PhotoFrame.java deleted file mode 100644 index 1151322270..0000000000 --- a/src/com/android/launcher/PhotoFrame.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageView; - - -/** - * Desktop widget that holds a user folder - * - */ -public class PhotoFrame extends ImageView implements OnClickListener { - - public PhotoFrame(Context context, AttributeSet attrs) { - super(context, attrs); - setClickable(true); - setOnClickListener(this); - setWillNotCacheDrawing(true); - } - - public void onClick(View v) { - ((Launcher) mContext).updatePhotoFrame((Widget) getTag()); - } -} diff --git a/src/com/android/launcher/Search.java b/src/com/android/launcher/Search.java index 41d6562c7a..97dcd980be 100644 --- a/src/com/android/launcher/Search.java +++ b/src/com/android/launcher/Search.java @@ -16,8 +16,6 @@ package com.android.launcher; -import java.util.List; - import android.app.ISearchManager; import android.app.SearchManager; import android.content.ActivityNotFoundException; @@ -62,10 +60,11 @@ import android.widget.AdapterView.OnItemSelectedListener; public class Search extends LinearLayout implements OnClickListener, OnKeyListener, OnLongClickListener, TextWatcher, OnItemClickListener, OnItemSelectedListener { - private final String TAG = "SearchGadget"; + private final String TAG = "SearchWidget"; private AutoCompleteTextView mSearchText; private ImageButton mGoButton; + private ImageButton mVoiceButton; private OnLongClickListener mLongClickListener; // Support for suggestions @@ -76,7 +75,11 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen private String mSuggestionQuery = null; private int mItemSelected = -1; + // For voice searching + private Intent mVoiceSearchIntent; + private Rect mTempRect = new Rect(); + private boolean mRestoreFocus = false; /** * Used to inflate the Workspace from XML. @@ -86,6 +89,10 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen */ public Search(Context context, AttributeSet attrs) { super(context, attrs); + + mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); + mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL, + android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); } /** @@ -94,6 +101,14 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen public void onClick(View v) { if (v == mGoButton) { query(); + } else if (v == mVoiceButton) { + try { + getContext().startActivity(mVoiceSearchIntent); + } catch (ActivityNotFoundException ex) { + // Should not happen, since we check the availability of + // voice search before showing the button. But just in case... + Log.w(TAG, "Could not find voice search activity"); + } } } @@ -154,7 +169,26 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen getContext().startActivity(launcher); } - + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + if (!hasWindowFocus && hasFocus()) { + mRestoreFocus = true; + } + + super.onWindowFocusChanged(hasWindowFocus); + + if (hasWindowFocus && mRestoreFocus) { + if (isInTouchMode()) { + final AutoCompleteTextView searchText = mSearchText; + searchText.setSelectAllOnFocus(false); + searchText.requestFocusFromTouch(); + searchText.setSelectAllOnFocus(true); + } + mRestoreFocus = false; + } + } + /** * Implements TextWatcher (for EditText) */ @@ -206,7 +240,7 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen return true; } } - } else if (v == mGoButton) { + } else if (v == mGoButton || v == mVoiceButton) { boolean handled = false; if (!event.isSystem() && (keyCode != KeyEvent.KEYCODE_DPAD_UP) && @@ -243,7 +277,14 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - requestFocusFromTouch(); + // Request focus unless the user tapped on the voice search button + final int x = (int) ev.getX(); + final int y = (int) ev.getY(); + final Rect frame = mTempRect; + mVoiceButton.getHitRect(frame); + if (!frame.contains(x, y)) { + requestFocusFromTouch(); + } return super.onInterceptTouchEvent(ev); } @@ -268,26 +309,29 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen mSearchText.addTextChangedListener(this); mGoButton = (ImageButton) findViewById(R.id.search_go_btn); + mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn); mGoButton.setOnClickListener(this); + mVoiceButton.setOnClickListener(this); mGoButton.setOnKeyListener(this); + mVoiceButton.setOnKeyListener(this); mSearchText.setOnLongClickListener(this); mGoButton.setOnLongClickListener(this); + mVoiceButton.setOnLongClickListener(this); // disable the button since we start out w/empty input mGoButton.setEnabled(false); mGoButton.setFocusable(false); + configureSearchableInfo(); configureSuggestions(); + configureVoiceSearchButton(); } - /** The rest of the class deals with providing search suggestions */ - /** - * Set up the suggestions provider mechanism + * Read the searchable info from the search manager */ - private void configureSuggestions() { - // get SearchableInfo + private void configureSearchableInfo() { ISearchManager sms; SearchableInfo searchable; sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE)); @@ -303,6 +347,36 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen return; } mSearchable = searchable; + } + + /** + * If appropriate & available, configure voice search + * + * Note: Because the home screen search widget is always web search, we only check for + * getVoiceSearchLaunchWebSearch() modes. We don't support the alternate form of app-specific + * voice search. + */ + private void configureVoiceSearchButton() { + boolean voiceSearchVisible = false; + if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) { + // Enable the voice search button if there is an activity that can handle it + PackageManager pm = getContext().getPackageManager(); + ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent, + PackageManager.MATCH_DEFAULT_ONLY); + voiceSearchVisible = ri != null; + } + + // finally, set visible state of voice search button, as appropriate + mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE); + } + + /** The rest of the class deals with providing search suggestions */ + + /** + * Set up the suggestions provider mechanism + */ + private void configureSuggestions() { + // get SearchableInfo mSearchText.setOnItemClickListener(this); mSearchText.setOnItemSelectedListener(this); @@ -314,6 +388,18 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen mSearchText.setAdapter(mSuggestionsAdapter); } + /** + * Remove internal cursor references when detaching from window which + * prevents {@link Context} leaks. + */ + @Override + public void onDetachedFromWindow() { + if (mSuggestionsAdapter != null) { + mSuggestionsAdapter.changeCursor(null); + mSuggestionsAdapter = null; + } + } + /** * Implements OnItemClickListener */ @@ -443,7 +529,11 @@ public class Search extends LinearLayout implements OnClickListener, OnKeyListen " returned exception" + e.toString()); } } - + + SearchAutoCompleteTextView getSearchInputField() { + return (SearchAutoCompleteTextView) mSearchText; + } + /** * This class provides the filtering-based interface to suggestions providers. * It is hardwired in a couple of places to support GoogleSearch - for example, it supports diff --git a/src/com/android/launcher/SearchAutoCompleteTextView.java b/src/com/android/launcher/SearchAutoCompleteTextView.java new file mode 100644 index 0000000000..e25a8f19e9 --- /dev/null +++ b/src/com/android/launcher/SearchAutoCompleteTextView.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher; + +import android.widget.AutoCompleteTextView; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.graphics.Rect; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.app.Activity; + +/** + * This class is not for the faint of heart. Home works in the pan & scan + * soft input mode. However, this mode gets rid of the soft keyboard on rotation, + * which is a probelm when the Search widget has focus. This special class + * changes Home's soft input method temporarily as long as the Search widget holds + * the focus. This way, the soft keyboard remains after rotation. + */ +public class SearchAutoCompleteTextView extends AutoCompleteTextView { + private boolean mShowKeyboard; + + private Handler mLoseFocusHandler = new Handler() { + public void handleMessage(Message msg) { + if (msg.what == 1 && !hasFocus()) { + // Hide the soft keyboard when the search widget loses the focus + InputMethodManager.peekInstance().hideSoftInputFromWindow(getWindowToken(), 0); + } + } + }; + + public SearchAutoCompleteTextView(Context context) { + super(context); + } + + public SearchAutoCompleteTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SearchAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + + final WindowManager.LayoutParams lp = ((Activity) getContext()).getWindow().getAttributes(); + if (gainFocus) { + lp.softInputMode = + (lp.softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) | + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; + } else { + //noinspection PointlessBitwiseExpression + lp.softInputMode = + (lp.softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) | + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; + // If we don't immediately gain focus, we want to hide the IME. + mLoseFocusHandler.sendEmptyMessage(1); + } + + if (getWindowToken() != null) { + final WindowManager manager = (WindowManager) + getContext().getSystemService(Context.WINDOW_SERVICE); + manager.updateViewLayout(getRootView(), lp); + + if (mShowKeyboard) { + if (getContext().getResources().getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES) { + InputMethodManager inputManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(this, 0); + } + mShowKeyboard = false; + } + } + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + // See Workspace#focusOnSearch() + setFocusableInTouchMode(false); + } + + void showKeyboardOnNextFocus() { + mShowKeyboard = true; + } +} diff --git a/src/com/android/launcher/WallpaperChooser.java b/src/com/android/launcher/WallpaperChooser.java index afbe6f352b..9ac922cba5 100644 --- a/src/com/android/launcher/WallpaperChooser.java +++ b/src/com/android/launcher/WallpaperChooser.java @@ -50,7 +50,6 @@ public class WallpaperChooser extends Activity implements AdapterView.OnItemSele R.drawable.wallpaper_path_small, R.drawable.wallpaper_sunrise_small, R.drawable.wallpaper_mountain_small, - R.drawable.wallpaper_ripples_small, R.drawable.wallpaper_road_small, R.drawable.wallpaper_jellyfish_small, R.drawable.wallpaper_zanzibar_small, @@ -61,14 +60,13 @@ public class WallpaperChooser extends Activity implements AdapterView.OnItemSele }; private static final Integer[] IMAGE_IDS = { - R.drawable.wallpaper_lake, + com.android.internal.R.drawable.default_wallpaper, R.drawable.wallpaper_sunset, R.drawable.wallpaper_beach, R.drawable.wallpaper_snow_leopard, R.drawable.wallpaper_path, R.drawable.wallpaper_sunrise, R.drawable.wallpaper_mountain, - R.drawable.wallpaper_ripples, R.drawable.wallpaper_road, R.drawable.wallpaper_jellyfish, R.drawable.wallpaper_zanzibar, @@ -123,9 +121,6 @@ public class WallpaperChooser extends Activity implements AdapterView.OnItemSele final String[] extras = resources.getStringArray(R.array.extra_wallpapers); final String packageName = getApplication().getPackageName(); - final ArrayList images = mImages; - final ArrayList thumbs = mThumbs; - for (String extra : extras) { int res = resources.getIdentifier(extra, "drawable", packageName); if (res != 0) { @@ -133,8 +128,8 @@ public class WallpaperChooser extends Activity implements AdapterView.OnItemSele "drawable", packageName); if (thumbRes != 0) { - images.add(res); - thumbs.add(res); + mThumbs.add(thumbRes); + mImages.add(res); } } } @@ -148,7 +143,7 @@ public class WallpaperChooser extends Activity implements AdapterView.OnItemSele public void onItemSelected(AdapterView parent, View v, int position, long id) { final ImageView view = mImageView; - Bitmap b = BitmapFactory.decodeResource(getResources(), IMAGE_IDS[position], mOptions); + Bitmap b = BitmapFactory.decodeResource(getResources(), mImages.get(position), mOptions); view.setImageBitmap(b); // Help the GC diff --git a/src/com/android/launcher/Widget.java b/src/com/android/launcher/Widget.java index 8812522222..4f246cc77a 100644 --- a/src/com/android/launcher/Widget.java +++ b/src/com/android/launcher/Widget.java @@ -20,32 +20,11 @@ import android.content.ContentValues; import android.graphics.Bitmap; /** - * Represents one instance of a Launcher widget (clock, search, photo frame). - * + * Represents one instance of a Launcher widget, such as search. */ class Widget extends ItemInfo { - int layoutResource; - Bitmap photo; - static Widget makeClock() { - Widget w = new Widget(); - w.itemType = LauncherSettings.Favorites.ITEM_TYPE_WIDGET_CLOCK; - w.spanX = 2; - w.spanY = 2; - w.layoutResource = R.layout.widget_clock; - return w; - } - - static Widget makePhotoFrame() { - Widget w = new Widget(); - w.itemType = LauncherSettings.Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME; - w.spanX = 2; - w.spanY = 2; - w.layoutResource = R.layout.widget_photo_frame; - return w; - } - static Widget makeSearch() { Widget w = new Widget(); w.itemType = LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH; @@ -54,11 +33,4 @@ class Widget extends ItemInfo { w.layoutResource = R.layout.widget_search; return w; } - - @Override - void onAddToDatabase(ContentValues values) { - super.onAddToDatabase(values); - writeBitmap(values, photo); - } - } diff --git a/src/com/android/launcher/Workspace.java b/src/com/android/launcher/Workspace.java index 0ca1b5af68..bc5347e7a5 100644 --- a/src/com/android/launcher/Workspace.java +++ b/src/com/android/launcher/Workspace.java @@ -17,12 +17,15 @@ package com.android.launcher; import android.content.Context; +import android.content.Intent; +import android.content.ComponentName; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Rect; +import android.graphics.Region; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -89,6 +92,13 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag private boolean mAllowLongPress; private boolean mLocked; + private int mTouchSlop; + + final Rect mDrawerBounds = new Rect(); + final Rect mClipBounds = new Rect(); + int mDrawerContentHeight; + int mDrawerContentWidth; + /** * Used to inflate the Workspace from XML. * @@ -126,6 +136,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag mPaint = new Paint(); mPaint.setDither(false); + + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } /** @@ -433,6 +445,29 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag @Override protected void dispatchDraw(Canvas canvas) { + boolean restore = false; + + // If the all apps drawer is open and the drawing region for the workspace + // is contained within the drawer's bounds, we skip the drawing. This requires + // the drawer to be fully opaque. + if (mLauncher.isDrawerUp()) { + final Rect clipBounds = mClipBounds; + canvas.getClipBounds(clipBounds); + clipBounds.offset(-mScrollX, -mScrollY); + if (mDrawerBounds.contains(clipBounds)) { + return; + } + } else if (mLauncher.isDrawerMoving()) { + restore = true; + canvas.save(Canvas.CLIP_SAVE_FLAG); + + final View view = mLauncher.getDrawerHandle(); + final int top = view.getTop() + view.getHeight(); + + canvas.clipRect(mScrollX, top, mScrollX + mDrawerContentWidth, + top + mDrawerContentHeight, Region.Op.DIFFERENCE); + } + float x = mScrollX * mWallpaperOffset; if (x + mWallpaperWidth < mRight - mLeft) { x = mRight - mLeft - mWallpaperWidth; @@ -464,6 +499,10 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } } } + + if (restore) { + canvas.restore(); + } } @Override @@ -626,8 +665,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag */ final int xDiff = (int) Math.abs(x - mLastMotionX); final int yDiff = (int) Math.abs(y - mLastMotionY); - final int touchSlop = ViewConfiguration.getTouchSlop(); - + + final int touchSlop = mTouchSlop; boolean xMoved = xDiff > touchSlop; boolean yMoved = yDiff > touchSlop; @@ -669,6 +708,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag // Release the drag clearChildrenCache(); mTouchState = TOUCH_STATE_REST; + mAllowLongPress = false; break; } @@ -835,11 +875,16 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) { + addApplicationShortcut(info, cellInfo, false); + } + + void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo, + boolean insertAtFirst) { final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen); final int[] result = new int[2]; layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result); - onDropExternal(result[0], result[1], info, layout); + onDropExternal(result[0], result[1], info, layout, insertAtFirst); } public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) { @@ -878,6 +923,11 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) { + onDropExternal(x, y, dragInfo, cellLayout, false); + } + + private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout, + boolean insertAtFirst) { // Drag from somewhere else ItemInfo info = (ItemInfo) dragInfo; @@ -901,7 +951,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag throw new IllegalStateException("Unknown item type: " + info.itemType); } - cellLayout.addView(view); + cellLayout.addView(view, insertAtFirst ? 0 : -1); view.setOnLongClickListener(mLongClickListener); cellLayout.onDropChild(view, x, y); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); @@ -979,12 +1029,12 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag /** * Find a search widget on the given screen */ - private View findSearchWidget(CellLayout screen) { + private Search findSearchWidget(CellLayout screen) { final int count = screen.getChildCount(); for (int i = 0; i < count; i++) { View v = screen.getChildAt(i); if (v instanceof Search) { - return v; + return (Search) v; } } return null; @@ -995,9 +1045,30 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag * if there is one. Also clears the current search selection so we don't */ private boolean focusOnSearch(int screen) { - CellLayout currentScreen = (CellLayout)getChildAt(screen); - Search searchWidget = (Search)findSearchWidget(currentScreen); + CellLayout currentScreen = (CellLayout) getChildAt(screen); + final Search searchWidget = findSearchWidget(currentScreen); if (searchWidget != null) { + // This is necessary when focus on search is requested from the menu + // If the workspace was not in touch mode before the menu is invoked + // and the user clicks "Search" by touching the menu item, the following + // happens: + // + // - We request focus from touch on the search widget + // - The search widget gains focus + // - The window focus comes back to Home's window + // - The touch mode change is propagated to Home's window + // - The search widget is not focusable in touch mode and ViewRoot + // clears its focus + // + // Forcing focusable in touch mode ensures the search widget will + // keep the focus no matter what happens. + // + // Note: the search input field disables focusable in touch mode + // after the window gets the focus back, see SearchAutoCompleteTextView + final SearchAutoCompleteTextView input = searchWidget.getSearchInputField(); + input.setFocusableInTouchMode(true); + input.showKeyboardOnNextFocus(); + if (isInTouchMode()) { searchWidget.requestFocusFromTouch(); } else { @@ -1124,6 +1195,14 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag public boolean allowLongPress() { return mAllowLongPress; } + + /** + * Set true to allow long-press events to be triggered, usually checked by + * {@link Launcher} to accept or block dpad-initiated long-presses. + */ + public void setAllowLongPress(boolean allowLongPress) { + mAllowLongPress = allowLongPress; + } void removeShortcutsForPackage(String packageName) { final ArrayList childrenToRemove = new ArrayList(); @@ -1138,7 +1217,13 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag Object tag = view.getTag(); if (tag instanceof ApplicationInfo) { ApplicationInfo info = (ApplicationInfo) tag; - if (packageName.equals(info.intent.getComponent().getPackageName())) { + // We need to check for ACTION_MAIN otherwise getComponent() might + // return null for some shortcuts (for instance, for shortcuts to + // web pages.) + final Intent intent = info.intent; + final ComponentName name = intent.getComponent(); + if (Intent.ACTION_MAIN.equals(intent.getAction()) && + name != null && packageName.equals(name.getPackageName())) { model.removeDesktopItem(info); LauncherModel.deleteItemFromDatabase(mLauncher, info); childrenToRemove.add(view); @@ -1155,6 +1240,10 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } } } + + // TODO: remove widgets when appwidgetmanager tells us they're gone +// void removeAppWidgetsForProvider() { +// } void moveToDefaultScreen() { snapToScreen(mDefaultScreen);