diff options
80 files changed, 1800 insertions, 374 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 59a2236..c401ef7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -6,6 +6,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="com.google.android.gallery3d.permission.PICASA_STORE" /> + <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="17"/> <application android:label="@string/app_name" diff --git a/res/layout-sw800dp/table.xml b/res/layout-sw800dp/table.xml index 0b67524..e063cd5 100644 --- a/res/layout-sw800dp/table.xml +++ b/res/layout-sw800dp/table.xml @@ -21,7 +21,22 @@ android:background="@+drawable/table" android:id="@+id/table" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" > + + <FrameLayout + android:id="@+id/stageleft" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + + <FrameLayout + android:id="@+id/background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layerType="hardware" + /> + + </com.android.dreams.phototable.PhotoTable> <!-- View android:background="@+drawable/vignette_br" android:layout_gravity="bottom|right" diff --git a/res/layout/album.xml b/res/layout/album.xml index 86ed595..b0aaaa2 100644 --- a/res/layout/album.xml +++ b/res/layout/album.xml @@ -20,6 +20,8 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" + android:focusable="true" + android:clickable="true" android:background="?android:attr/selectableItemBackground" > <TextView diff --git a/res/layout/settingslist.xml b/res/layout/settingslist.xml index 1f2128b..c383856 100644 --- a/res/layout/settingslist.xml +++ b/res/layout/settingslist.xml @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - <merge xmlns:android="http://schemas.android.com/apk/res/android"> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> <ListView android:id="@android:id/list" android:layout_width="match_parent" @@ -22,11 +22,28 @@ android:paddingRight="16dp" /> - <ProgressBar android:id="@android:id/empty" - style="?android:attr/progressBarStyleLarge" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - /> + <FrameLayout + android:id="@android:id/empty" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <ProgressBar + android:id="@+id/spinner" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + + <TextView + android:id="@+id/sorry" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0.5" + android:gravity="center_vertical|center_horizontal" + android:text="@string/no_photos" + android:textColor="@android:color/white" + android:textSize="20sp" + android:visibility="gone" /> + </FrameLayout> </merge> diff --git a/res/layout/table.xml b/res/layout/table.xml index b5663cb..7cdb51a 100644 --- a/res/layout/table.xml +++ b/res/layout/table.xml @@ -18,12 +18,28 @@ android:layout_width="match_parent" android:layout_height="match_parent" > + <com.android.dreams.phototable.PhotoTable - android:background="@+drawable/table" - android:id="@+id/table" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> + android:id="@+id/table" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@+drawable/table" + android:focusable="true" > + + <FrameLayout + android:id="@+id/stageleft" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + + <FrameLayout + android:id="@+id/background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layerType="hardware" + /> + + </com.android.dreams.phototable.PhotoTable> <!-- View android:background="@+drawable/vignette_br" android:layout_gravity="bottom|right" diff --git a/res/menu/photodream_settings_menu.xml b/res/menu/photodream_settings_menu.xml new file mode 100644 index 0000000..3ae368f --- /dev/null +++ b/res/menu/photodream_settings_menu.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/photodream_menu_all" + android:title="@string/photodream_select_all" + android:showAsAction="ifRoom" + /> +</menu> diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index 20fd794..d0be698 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Voorraadfoto\'s"</string> <string name="local_source_name" msgid="6487253091085518505">"Foto\'s op die toestel"</string> <string name="need_to_configure" msgid="1269102638159389032">"Geen foto\'s gekies nie."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Kies almal"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Ontkies almal"</string> + <string name="no_photos" msgid="4262083523147564199">"Geen foto\'s op die toestel nie."</string> </resources> diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index d8e848e..5a07a36 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"የክምችት ፎቶዎች"</string> <string name="local_source_name" msgid="6487253091085518505">"በመሣሪያ ላይ ያሉ መሣሪያዎች"</string> <string name="need_to_configure" msgid="1269102638159389032">"ምንም ፎቶዎች አልተመረጡም።"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"ሁሉንም ምረጥ"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"ሁሉንም አትምረጥ"</string> + <string name="no_photos" msgid="4262083523147564199">"በመሣሪያው ላይ ምንም ፎቶዎች የሉም።"</string> </resources> diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 1e16048..8c15d8c 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"صور المخزون"</string> <string name="local_source_name" msgid="6487253091085518505">"الصور على الجهاز"</string> <string name="need_to_configure" msgid="1269102638159389032">"لم يتم تحديد أي صور."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"تحديد الكل"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"إلغاء تحديد الكل"</string> + <string name="no_photos" msgid="4262083523147564199">"لا يحتوي الجهاز على أية صور."</string> </resources> diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 3be087b..45a7a41 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Захаваныя фотаздымкi"</string> <string name="local_source_name" msgid="6487253091085518505">"Фота на прыладзе"</string> <string name="need_to_configure" msgid="1269102638159389032">"Няма выбраных фотаздымкаў."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Выбраць усё"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Адмяніць выбар усяго"</string> + <string name="no_photos" msgid="4262083523147564199">"На прыладзе няма фатаграфій."</string> </resources> diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index acf55c8..0097b4d 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Стокови снимки"</string> <string name="local_source_name" msgid="6487253091085518505">"Снимки на устройството"</string> <string name="need_to_configure" msgid="1269102638159389032">"Няма избрани снимки."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Избор на всичко"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Без избрани"</string> + <string name="no_photos" msgid="4262083523147564199">"Няма снимки в устройството."</string> </resources> diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index e9c1bae..ce1ba91 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotos en estoc"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotos del dispositiu"</string> <string name="need_to_configure" msgid="1269102638159389032">"No s\'ha seleccionat cap foto."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Selec. tot"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Anul. sel. tot"</string> + <string name="no_photos" msgid="4262083523147564199">"No hi ha cap foto al dispositiu."</string> </resources> diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index b6962e0..a52ae07 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotky z fotobanky"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotky v zařízení"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nejsou vybrány žádné fotky."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Vybrat vše"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Zrušit výběr"</string> + <string name="no_photos" msgid="4262083523147564199">"V zařízení nejsou žádné fotografie."</string> </resources> diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index c0fd023..e7a96cb 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Arkivfotos"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotos på enheden"</string> <string name="need_to_configure" msgid="1269102638159389032">"Ingen fotos valgt."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Vælg alle"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Fravælg alle"</string> + <string name="no_photos" msgid="4262083523147564199">"Der er ingen billeder på enheden."</string> </resources> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 6b8f480..0d41eb2 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Stockfotos"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotos auf Gerät"</string> <string name="need_to_configure" msgid="1269102638159389032">"Keine Fotos ausgewählt"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Alle auswählen"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Keine auswählen"</string> + <string name="no_photos" msgid="4262083523147564199">"Keine Fotos auf dem Gerät"</string> </resources> diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 603c4f4..22dc407 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Φωτογραφίες στοκ"</string> <string name="local_source_name" msgid="6487253091085518505">"Φωτογραφίες στη συσκευή"</string> <string name="need_to_configure" msgid="1269102638159389032">"Δεν επιλέχθηκαν φωτογραφίες."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Επιλογή όλων"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Αποεπιλέξτε όλα"</string> + <string name="no_photos" msgid="4262083523147564199">"Δεν υπάρχουν φωτογραφίες στη συσκευή."</string> </resources> diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index 7aa02e4..0ea488f 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Stock photos"</string> <string name="local_source_name" msgid="6487253091085518505">"Photos on Device"</string> <string name="need_to_configure" msgid="1269102638159389032">"No photos selected."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Select All"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Deselect All"</string> + <string name="no_photos" msgid="4262083523147564199">"No photos on the device."</string> </resources> diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index a7a5e8a..e202b79 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotos en stock"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotos en el dispositivo"</string> <string name="need_to_configure" msgid="1269102638159389032">"No hay fotos seleccionadas."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Marcar todos"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Desmarcar todos"</string> + <string name="no_photos" msgid="4262083523147564199">"No hay fotos en el dispositivo."</string> </resources> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index f72ee16..7d17cd1 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotos de archivo"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotos del dispositivo"</string> <string name="need_to_configure" msgid="1269102638159389032">"No se ha seleccionado ninguna foto."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Seleccionar todo"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Desmarcar todo"</string> + <string name="no_photos" msgid="4262083523147564199">"No hay fotos en el dispositivo."</string> </resources> diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 061f4b7..2a0c3d1 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotovaramu"</string> <string name="local_source_name" msgid="6487253091085518505">"Seadmesse salvestatud fotod"</string> <string name="need_to_configure" msgid="1269102638159389032">"Valitud ei ole ühtegi fotot."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Vali kõik"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Tühista kõik valikud"</string> + <string name="no_photos" msgid="4262083523147564199">"Seadmes ei ole fotosid."</string> </resources> diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 83408b2..0d3c9e0 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"عکسهای موجود"</string> <string name="local_source_name" msgid="6487253091085518505">"عکسهای موجود در دستگاه"</string> <string name="need_to_configure" msgid="1269102638159389032">"عکسی انتخاب نشده است."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"انتخاب همه"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"لغو انتخاب همه"</string> + <string name="no_photos" msgid="4262083523147564199">"هیچ عکسی در دستگاه نیست."</string> </resources> diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 1ec7f15..640cc1b 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Varastossa olevat kuvat"</string> <string name="local_source_name" msgid="6487253091085518505">"Kuvat laitteella"</string> <string name="need_to_configure" msgid="1269102638159389032">"Ei valittuja kuvia."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Valitse kaikki"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Poista kaikki valinnat"</string> + <string name="no_photos" msgid="4262083523147564199">"Laitteella ei ole kuvia."</string> </resources> diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 92b4a1b..5f79ad2 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Banque de photos"</string> <string name="local_source_name" msgid="6487253091085518505">"Photos de l\'appareil"</string> <string name="need_to_configure" msgid="1269102638159389032">"Aucune photo sélectionnée."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Tout sélectionner"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Tout désélectionner"</string> + <string name="no_photos" msgid="4262083523147564199">"Aucune photo sur l\'appareil"</string> </resources> diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index aeb7c9c..40eaad2 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"स्टॉक फ़ोटो"</string> <string name="local_source_name" msgid="6487253091085518505">"उपकरण पर फ़ोटो"</string> <string name="need_to_configure" msgid="1269102638159389032">"कोई फ़ोटो नहीं चुना गया."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"सभी को चुनें"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"सभी अचयनित करें"</string> + <string name="no_photos" msgid="4262083523147564199">"उपकरण पर कोई फ़ोटो नहीं है."</string> </resources> diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 9e01908..bb62c42 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Arhivske fotografije"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotografije na uređaju"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nije odabrana nijedna fotografija."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Odaberi sve"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Poništi sve"</string> + <string name="no_photos" msgid="4262083523147564199">"Na uređaju nema fotografija."</string> </resources> diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 257431e..2d3855f 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotótárfotók"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotók az eszközön"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nincsenek fotók kiválasztva."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Összes"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Semelyik"</string> + <string name="no_photos" msgid="4262083523147564199">"Nincsenek fényképek az eszközön."</string> </resources> diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 06d3926..758e830 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Foto Stok"</string> <string name="local_source_name" msgid="6487253091085518505">"Foto di Perangkat"</string> <string name="need_to_configure" msgid="1269102638159389032">"Tidak ada foto yang dipilih."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Pilih Semua"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Batalkan Semua"</string> + <string name="no_photos" msgid="4262083523147564199">"Tidak ada foto di perangkat."</string> </resources> diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 9a35e93..fd50f8c 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Foto di archivi stock"</string> <string name="local_source_name" msgid="6487253091085518505">"Foto su dispositivo"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nessuna foto selezionata."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Seleziona tutto"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Desel. tutto"</string> + <string name="no_photos" msgid="4262083523147564199">"Nessuna foto sul dispositivo."</string> </resources> diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index cc7b635..49b9c4f 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"תמונות במלאי"</string> <string name="local_source_name" msgid="6487253091085518505">"תמונות במכשיר"</string> <string name="need_to_configure" msgid="1269102638159389032">"לא נבחרו תמונות."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"בחר הכל"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"בטל את כל הבחירות"</string> + <string name="no_photos" msgid="4262083523147564199">"אין תמונות במכשיר."</string> </resources> diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index a5d613b..722faa9 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"ストックフォト"</string> <string name="local_source_name" msgid="6487253091085518505">"端末上の画像"</string> <string name="need_to_configure" msgid="1269102638159389032">"画像が選択されていません。"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"すべて選択"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"選択をすべて解除"</string> + <string name="no_photos" msgid="4262083523147564199">"端末に画像がありません。"</string> </resources> diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 2d470db..29b8aef 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"스톡 사진"</string> <string name="local_source_name" msgid="6487253091085518505">"기기에 저장된 사진"</string> <string name="need_to_configure" msgid="1269102638159389032">"선택한 사진이 없습니다."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"모두 선택"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"모두 선택취소"</string> + <string name="no_photos" msgid="4262083523147564199">"기기에 사진이 없습니다."</string> </resources> diff --git a/res/values-land-notouch/config.xml b/res/values-land-notouch/config.xml new file mode 100644 index 0000000..d125dd1 --- /dev/null +++ b/res/values-land-notouch/config.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + + <!-- Maximum number of photos to leave on the table. --> + <integer name="table_capacity">6</integer> + + <!-- Number of images to discard at a time. --> + <integer name="redeal_count">2</integer> + + <!-- Parts per million ratio between image size on the table and screen size. --> + <integer name="table_ratio">333333</integer> + + <!-- Duration in milliseconds for the pickup animation. --> + <integer name="photo_pickup_duration">1500</integer> + +</resources>
\ No newline at end of file diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 7520f85..f24ba05 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Šabloninės nuotraukos"</string> <string name="local_source_name" msgid="6487253091085518505">"Nuotraukos įrenginyje"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nėra pasirinktų nuotraukų."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Rinktis viską"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Panaikint žym."</string> + <string name="no_photos" msgid="4262083523147564199">"Įrenginyje nėra nuotraukų."</string> </resources> diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 490ca4f..dd3a161 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Krājuma fotoattēli"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotoattēli ierīcē"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nav atlasīts neviens fotoattēls."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Atlasīt visus"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Atcelt atlasi"</string> + <string name="no_photos" msgid="4262083523147564199">"Ierīcē nav neviena fotoattēla."</string> </resources> diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index f5aee42..0d1b9c0 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Foto Stok"</string> <string name="local_source_name" msgid="6487253091085518505">"Foto pada Peranti"</string> <string name="need_to_configure" msgid="1269102638159389032">"Tiada foto dipilih."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Pilih Semua"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Nyahpilih Semua"</string> + <string name="no_photos" msgid="4262083523147564199">"Tiada foto pada peranti."</string> </resources> diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 05522eb..3fc2057 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="6441943862284988546">"Bilder som skjermsparere"</string> - <string name="table_screensaver_name" msgid="1150228032617397055">"Bildetabell"</string> + <string name="table_screensaver_name" msgid="1150228032617397055">"Bildekollasj"</string> <string name="flipper_screensaver_name" msgid="6988572107391523141">"Bilderamme"</string> <string name="posts_album_name" msgid="5628151551239729515">"Bilder fra innlegg"</string> <string name="uploads_album_name" msgid="4057519060978630370">"Direkteopplastinger"</string> @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Bildedatabase"</string> <string name="local_source_name" msgid="6487253091085518505">"Bilder på enheten"</string> <string name="need_to_configure" msgid="1269102638159389032">"Ingen bilder valgt."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Merk alle"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Fjern all merking"</string> + <string name="no_photos" msgid="4262083523147564199">"Ingen bilder på enheten."</string> </resources> diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index c41cdf9..4cb6ac8 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Stockfoto\'s"</string> <string name="local_source_name" msgid="6487253091085518505">"Foto\'s op apparaat"</string> <string name="need_to_configure" msgid="1269102638159389032">"Geen foto\'s geselecteerd."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Alles selecteren"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Selec. opheffen"</string> + <string name="no_photos" msgid="4262083523147564199">"Geen foto\'s op het apparaat."</string> </resources> diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index d49b918..11ba127 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -19,10 +19,13 @@ <string name="app_name" msgid="6441943862284988546">"Wygaszacze ze zdjęciami"</string> <string name="table_screensaver_name" msgid="1150228032617397055">"Tablica ze zdjęciami"</string> <string name="flipper_screensaver_name" msgid="6988572107391523141">"Ramka foto"</string> - <string name="posts_album_name" msgid="5628151551239729515">"Zdjęcia z postów"</string> + <string name="posts_album_name" msgid="5628151551239729515">"Zdjęcia z wpisów"</string> <string name="uploads_album_name" msgid="4057519060978630370">"Z autoprzesyłania"</string> <string name="unknown_album_name" msgid="2135521368192091267">"Album bez nazwy"</string> <string name="stock_photo_album_name" msgid="3150527596227525460">"Zdjęcia licencjonowane"</string> <string name="local_source_name" msgid="6487253091085518505">"Zdjęcia na urządzeniu"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nie wybrano zdjęć."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Zaznacz wszystko"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Wyczyść wszystko"</string> + <string name="no_photos" msgid="4262083523147564199">"Brak zdjęć na tym urządzeniu."</string> </resources> diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 6b5276e..2d7152d 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Banco de Imagens"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotografias no Dispositivo"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nenhuma fotografia selecionada."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Selecionar Tudo"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Desmarcar Tudo"</string> + <string name="no_photos" msgid="4262083523147564199">"Sem fotografias no dispositivo."</string> </resources> diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index d06f11f..311f249 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Banco de fotos"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotos no dispositivo"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nenhuma foto selecionada."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Selecionar tudo"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Desmarcar tudo"</string> + <string name="no_photos" msgid="4262083523147564199">"Nenhuma foto no dispositivo."</string> </resources> diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 390ef57..a1a9259 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotografii din stoc"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotografii de pe gadget"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nu a fost selectată nicio fotografie."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Selectați tot"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Deselectați tot"</string> + <string name="no_photos" msgid="4262083523147564199">"Nu există fotografii pe dispozitiv."</string> </resources> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index aa259e6..de26942 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Снимки из фотобанков"</string> <string name="local_source_name" msgid="6487253091085518505">"Фото на устройстве"</string> <string name="need_to_configure" msgid="1269102638159389032">"Ничего не выбрано"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Выбрать все"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Отменить выбор"</string> + <string name="no_photos" msgid="4262083523147564199">"На устройстве нет фотографий."</string> </resources> diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index f317101..94ee9df 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotografie z fotobanky"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotografie v zariadení"</string> <string name="need_to_configure" msgid="1269102638159389032">"Nie sú vybraté žiadne fotografie."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Vybrať všetko"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Zrušiť výber"</string> + <string name="no_photos" msgid="4262083523147564199">"Zariadenie neobsahuje žiadne fotografie."</string> </resources> diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index f1076d5..3902906 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Fotografije na zalogi"</string> <string name="local_source_name" msgid="6487253091085518505">"Fotografije v napravi"</string> <string name="need_to_configure" msgid="1269102638159389032">"Izbrana ni nobena fotografija."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Izberi vse"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Počisti vse"</string> + <string name="no_photos" msgid="4262083523147564199">"V napravi ni fotografij."</string> </resources> diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 465309e..0fbd11e 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Сток фотографије"</string> <string name="local_source_name" msgid="6487253091085518505">"Слике на уређају"</string> <string name="need_to_configure" msgid="1269102638159389032">"Није изабрана ниједна слика."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Изабери све"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Опозови све"</string> + <string name="no_photos" msgid="4262083523147564199">"Нема слика на уређају."</string> </resources> diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index f374a67..a97a229 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Bildbyråfoton"</string> <string name="local_source_name" msgid="6487253091085518505">"Foton på enheten"</string> <string name="need_to_configure" msgid="1269102638159389032">"Inga foton har markerats."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Markera alla"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Avmarkera alla"</string> + <string name="no_photos" msgid="4262083523147564199">"Det finns inga foton på enheten."</string> </resources> diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 9b1392b..74edf3e 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -20,9 +20,12 @@ <string name="table_screensaver_name" msgid="1150228032617397055">"Jedwali la Picha"</string> <string name="flipper_screensaver_name" msgid="6988572107391523141">"Fremu ya Picha"</string> <string name="posts_album_name" msgid="5628151551239729515">"Picha kutoka kwenye Machapisho"</string> - <string name="uploads_album_name" msgid="4057519060978630370">"Vipakiaji vya Papo hapo"</string> + <string name="uploads_album_name" msgid="4057519060978630370">"Vipakiwa vya papo hapo"</string> <string name="unknown_album_name" msgid="2135521368192091267">"Albamu Isiyo na Jina"</string> <string name="stock_photo_album_name" msgid="3150527596227525460">"Picha za Akiba"</string> <string name="local_source_name" msgid="6487253091085518505">"Picha katika Kifaa"</string> <string name="need_to_configure" msgid="1269102638159389032">"Hakuna picha zilizochaguliwa."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Chagua Zote"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Usichague yoyote"</string> + <string name="no_photos" msgid="4262083523147564199">"Hakuna picha kwenye kifaa."</string> </resources> diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml index 4a8a67f..e1ad849 100644 --- a/res/values-sw600dp/config.xml +++ b/res/values-sw600dp/config.xml @@ -16,5 +16,8 @@ <resources> <!-- Parts per million ratio between image size and screen size. --> <integer name="image_ratio">750000</integer> + + <!-- Parts per million ratio between image size on the table and screen size. --> + <integer name="table_ratio">300000</integer> </resources> diff --git a/res/values-sw800dp/config.xml b/res/values-sw800dp/config.xml index eeefc3a..ec6a85f 100644 --- a/res/values-sw800dp/config.xml +++ b/res/values-sw800dp/config.xml @@ -15,12 +15,13 @@ --> <resources> <!-- Milliseconds between drops. --> - <integer name="table_drop_period">90000</integer> + <integer name="table_drop_period">75000</integer> <!-- Milliseconds to wait before the next fast drop.--> - <integer name="fast_drop">10000</integer> + <integer name="fast_drop">5000</integer> + + <!-- Duration in milliseconds for the pickup animation. --> + <integer name="photo_pickup_duration">1500</integer> - <!-- Parts per million ratio between image size and screen size. --> - <integer name="image_ratio">500000</integer> </resources> diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 254c269..8ae896b 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"คลังภาพ"</string> <string name="local_source_name" msgid="6487253091085518505">"รูปภาพในอุปกรณ์"</string> <string name="need_to_configure" msgid="1269102638159389032">"ไม่ได้เลือกรูปภาพไว้"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"เลือกทั้งหมด"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"ยกเลิกการเลือก"</string> + <string name="no_photos" msgid="4262083523147564199">"ไม่มีรูปภาพบนอุปกรณ์"</string> </resources> diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 8b3adef..ca83b8d 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Mga Stock na Larawan"</string> <string name="local_source_name" msgid="6487253091085518505">"Mga Larawan sa Device"</string> <string name="need_to_configure" msgid="1269102638159389032">"Walang piniling mga larawan."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Piliin Lahat"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"I-deselect Lahat"</string> + <string name="no_photos" msgid="4262083523147564199">"Walang mga larawan sa device."</string> </resources> diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index ea9f339..246082f 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Stok Fotoğraflar"</string> <string name="local_source_name" msgid="6487253091085518505">"Cihazdaki Fotoğraflar"</string> <string name="need_to_configure" msgid="1269102638159389032">"Seçili fotoğraf yok."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Tümünü Seç"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Hiçbirini Seçme"</string> + <string name="no_photos" msgid="4262083523147564199">"Cihazda hiç fotoğraf yok."</string> </resources> diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index ff3b2fa..ea56d1c 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Фотографії з фотобанку"</string> <string name="local_source_name" msgid="6487253091085518505">"Фотографії на пристрої"</string> <string name="need_to_configure" msgid="1269102638159389032">"Не вибрано жодної фотографії."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Вибрати все"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Відмінити все"</string> + <string name="no_photos" msgid="4262083523147564199">"На пристрої немає фотографій."</string> </resources> diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index a60dded..4b145f5 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Ảnh trên kho ảnh"</string> <string name="local_source_name" msgid="6487253091085518505">"Ảnh trên thiết bị"</string> <string name="need_to_configure" msgid="1269102638159389032">"Không có ảnh nào được chọn."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Chọn tất cả"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Bỏ chọn tất cả"</string> + <string name="no_photos" msgid="4262083523147564199">"Không có ảnh trên thiết bị."</string> </resources> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 08e628a..80b0790 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="6441943862284988546">"照片屏幕保护程序"</string> - <string name="table_screensaver_name" msgid="1150228032617397055">"照片表格"</string> + <string name="table_screensaver_name" msgid="1150228032617397055">"照片桌面"</string> <string name="flipper_screensaver_name" msgid="6988572107391523141">"相框"</string> <string name="posts_album_name" msgid="5628151551239729515">"信息中的照片"</string> <string name="uploads_album_name" msgid="4057519060978630370">"即时上传"</string> @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"图库照片"</string> <string name="local_source_name" msgid="6487253091085518505">"设备中的照片"</string> <string name="need_to_configure" msgid="1269102638159389032">"未选择任何照片。"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"全选"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"全部不选"</string> + <string name="no_photos" msgid="4262083523147564199">"设备上没有任何照片。"</string> </resources> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 4446557..a3242f1 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"圖庫相片"</string> <string name="local_source_name" msgid="6487253091085518505">"裝置上的相片"</string> <string name="need_to_configure" msgid="1269102638159389032">"未選取任何相片。"</string> + <string name="photodream_select_all" msgid="6364858563337927827">"全選"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"取消全選"</string> + <string name="no_photos" msgid="4262083523147564199">"裝置上沒有任何相片。"</string> </resources> diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 4965da9..885a2bf 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -25,4 +25,7 @@ <string name="stock_photo_album_name" msgid="3150527596227525460">"Izithombe zesitoko"</string> <string name="local_source_name" msgid="6487253091085518505">"Izithombe kudivayisi"</string> <string name="need_to_configure" msgid="1269102638159389032">"Azikho izithombe ezikhethiwe."</string> + <string name="photodream_select_all" msgid="6364858563337927827">"Khetha konke"</string> + <string name="photodream_select_none" msgid="8816452124894525861">"Ungakhethi konke"</string> + <string name="no_photos" msgid="4262083523147564199">"Azikho izithombe kudivayisi."</string> </resources> diff --git a/res/values/colors.xml b/res/values/colors.xml index a6c7746..ec487e7 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -18,4 +18,5 @@ <color name="tabletop_dark">#ff222222</color> <color name="vignette_light">#00000000</color> <color name="vignette_dark">#bf000000</color> + <color name="highlight_color">@android:color/holo_blue_bright</color> </resources> diff --git a/res/values/config.xml b/res/values/config.xml index d8d745d..c3a6e65 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -18,7 +18,7 @@ <integer name="table_drop_period">45000</integer> <!-- Milliseconds to wait before the next fast drop.--> - <integer name="fast_drop">4000</integer> + <integer name="fast_drop">3000</integer> <!-- Milliseconds between drops. --> <integer name="carousel_drop_period">10000</integer> @@ -33,7 +33,7 @@ <integer name="table_capacity">10</integer> <!-- Number of images to discard at a time.--> - <integer name="redeal_count">3</integer> + <integer name="redeal_count">5</integer> <!-- Parts per million ratio between image size and screen size. --> <integer name="image_ratio">500000</integer> @@ -85,5 +85,29 @@ <!-- Number of images to pre-load. --> <integer name="num_images_to_preload">5</integer> + + <!-- distance from edge to start a valid edge swipe as a fraction of + touch area width, in parts per million from 0 to 1. --> + <integer name="table_edge_swipe_gutter">50000</integer> + + <!-- minimum length of a valid edge swipe as a fraction of + touch area width, in parts per million from 0 to 1. --> + <integer name="table_edge_swipe_threshold">100000</integer> + + <!-- Parts per million gain applied to generalized touch gestures. --> + <integer name="generalized_touch_gain">2000000</integer> + + <!-- Enable story mode. --> + <bool name="enable_story_mode">true</bool> + + <!-- Duration in milliseconds for the pickup animation. --> + <integer name="photo_pickup_duration">1000</integer> + + <!-- Milliseconds that the selection will remain without user interaction. --> + <integer name="max_selection_time">30000</integer> + + <!-- Milliseconds that the focus will remain without user interaction. --> + <integer name="max_focus_time">5000</integer> + </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 858d6c1..1f64e10 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -40,4 +40,13 @@ <!-- Instruct the user to configure the screensaver in settings to see photos. --> <string name="need_to_configure">No photos selected.</string> + + <!-- Click to select all albums in the album selection activity. [CHAR LIMIT=15] --> + <string name="photodream_select_all">Select All</string> + + <!-- Click to clear selection in the album selection activity. [CHAR LIMIT=15] --> + <string name="photodream_select_none">Deselect All</string> + + <!-- Text shown instead of a list of albums when there are no albums with photos. [CHAR LIMIT=50] --> + <string name="no_photos">No photos on the device.</string> </resources> diff --git a/src/com/android/dreams/phototable/AlbumDataAdapter.java b/src/com/android/dreams/phototable/AlbumDataAdapter.java index a0c039b..570bbd7 100644 --- a/src/com/android/dreams/phototable/AlbumDataAdapter.java +++ b/src/com/android/dreams/phototable/AlbumDataAdapter.java @@ -17,7 +17,6 @@ package com.android.dreams.phototable; import android.content.Context; import android.content.SharedPreferences; -import android.text.SpannableString; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -30,7 +29,6 @@ import android.widget.TextView; import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Set; /** * Settings panel for photo flipping dream. @@ -45,6 +43,7 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> { private final LayoutInflater mInflater; private final int mLayout; private final ItemClickListener mListener; + private final HashSet<String> mValidAlbumIds; public AlbumDataAdapter(Context context, SharedPreferences settings, int resource, List<PhotoSource.AlbumData> objects) { @@ -54,11 +53,29 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> { mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mListener = new ItemClickListener(); - HashSet<String> validAlbumIds = new HashSet<String>(objects.size()); + mValidAlbumIds = new HashSet<String>(objects.size()); for (PhotoSource.AlbumData albumData: objects) { - validAlbumIds.add(albumData.id); + mValidAlbumIds.add(albumData.id); } - mSettings.pruneObsoleteSettings(validAlbumIds); + mSettings.pruneObsoleteSettings(mValidAlbumIds); + } + + public boolean isSelected(int position) { + PhotoSource.AlbumData data = getItem(position); + return mSettings.isAlbumEnabled(data.id); + } + + public boolean areAllSelected() { + return mSettings.areAllEnabled(mValidAlbumIds); + } + + public void selectAll(boolean select) { + if (select) { + mSettings.enableAllAlbums(mValidAlbumIds); + } else { + mSettings.disableAllAlbums(); + } + notifyDataSetChanged(); } @Override @@ -72,7 +89,7 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> { View vCheckBox = item.findViewById(R.id.enabled); if (vCheckBox != null && vCheckBox instanceof CheckBox) { CheckBox checkBox = (CheckBox) vCheckBox; - checkBox.setChecked(mSettings.isAlbumEnabled(data.id)); + checkBox.setChecked(isSelected(position)); checkBox.setTag(R.id.data_payload, data); } @@ -159,6 +176,7 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> { final boolean isChecked = !checkBox.isChecked(); checkBox.setChecked(isChecked); mSettings.setAlbumEnabled(data.id, isChecked); + notifyDataSetChanged(); if (DEBUG) Log.i(TAG, data.title + " is " + (isChecked ? "" : "not") + " enabled"); } else { diff --git a/src/com/android/dreams/phototable/AlbumSettings.java b/src/com/android/dreams/phototable/AlbumSettings.java index 23dda46..1ccd498 100644 --- a/src/com/android/dreams/phototable/AlbumSettings.java +++ b/src/com/android/dreams/phototable/AlbumSettings.java @@ -51,11 +51,16 @@ public class AlbumSettings { public boolean isAlbumEnabled(String albumId) { synchronized (mEnabledAlbums) { - boolean isEnabled = mEnabledAlbums.contains(albumId); return mEnabledAlbums.contains(albumId); } } + public boolean areAllEnabled(Collection<String> validAlbums) { + synchronized (mEnabledAlbums) { + return mEnabledAlbums.containsAll(validAlbums); + } + } + public void setAlbumEnabled(String albumId, boolean enabled) { if (isAlbumEnabled(albumId) != enabled) { synchronized (mEnabledAlbums) { @@ -70,6 +75,21 @@ public class AlbumSettings { } } + public void disableAllAlbums() { + synchronized (mEnabledAlbums) { + mEnabledAlbums.clear(); + writeEnabledAlbumsLocked(); + } + } + + public void enableAllAlbums(Collection<String> validAlbums) { + synchronized (mEnabledAlbums) { + mEnabledAlbums.clear(); + mEnabledAlbums.addAll(validAlbums); + writeEnabledAlbumsLocked(); + } + } + public void pruneObsoleteSettings(Collection<String> validAlbums) { if (!validAlbums.containsAll(mEnabledAlbums)) { synchronized (mEnabledAlbums) { diff --git a/src/com/android/dreams/phototable/CursorPhotoSource.java b/src/com/android/dreams/phototable/CursorPhotoSource.java new file mode 100644 index 0000000..f010a92 --- /dev/null +++ b/src/com/android/dreams/phototable/CursorPhotoSource.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dreams.phototable; + +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; + +/** + * Common implementation for sources that load images from a cursor. + */ +public abstract class CursorPhotoSource extends PhotoSource { + + // An invalid cursor position to represent the uninitialized state. + protected static final int UNINITIALIZED = -1; + // An invalid cursor position to represent the error state. + protected static final int INVALID = -2; + + public CursorPhotoSource(Context context, SharedPreferences settings) { + super(context, settings); + } + + public CursorPhotoSource(Context context, SharedPreferences settings, PhotoSource fallback) { + super(context, settings, fallback); + } + + @Override + protected ImageData naturalNext(ImageData current) { + if (current.cursor == null || current.cursor.isClosed()) { + openCursor(current); + } + findPosition(current); + current.cursor.moveToPosition(current.position); + current.cursor.moveToNext(); + ImageData data = null; + if (!current.cursor.isAfterLast()) { + data = unpackImageData(current.cursor, null); + data.cursor = current.cursor; + data.position = current.cursor.getPosition(); + } + return data; + } + + @Override + protected ImageData naturalPrevious(ImageData current) { + if (current.cursor == null || current.cursor.isClosed()) { + openCursor(current); + } + findPosition(current); + current.cursor.moveToPosition(current.position); + current.cursor.moveToPrevious(); + ImageData data = null; + if (!current.cursor.isBeforeFirst()) { + data = unpackImageData(current.cursor, null); + data.cursor = current.cursor; + data.position = current.cursor.getPosition(); + } + return data; + } + + @Override + protected void donePaging(ImageData current) { + if (current.cursor != null && !current.cursor.isClosed()) { + current.cursor.close(); + } + } + + protected abstract void openCursor(ImageData data); + protected abstract void findPosition(ImageData data); + protected abstract ImageData unpackImageData(Cursor cursor, ImageData data); +} + diff --git a/src/com/android/dreams/phototable/DragGestureDetector.java b/src/com/android/dreams/phototable/DragGestureDetector.java new file mode 100644 index 0000000..2153c48 --- /dev/null +++ b/src/com/android/dreams/phototable/DragGestureDetector.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dreams.phototable; + +import android.content.Context; +import android.content.res.Resources; +import android.view.MotionEvent; + +/** + * Detect and dispatch edge events. + */ +public class DragGestureDetector { + @SuppressWarnings("unused") + private static final String TAG = "DragGestureDetector"; + + private final PhotoTable mTable; + private final float mTouchGain; + + private float[] mLast; + private float[] mCurrent; + private boolean mDrag; + + public DragGestureDetector(Context context, PhotoTable table) { + Resources res = context.getResources(); + mTouchGain = res.getInteger(R.integer.generalized_touch_gain) / 1000000f; + mTable = table; + mLast = new float[2]; + mCurrent = new float[2]; + } + + private void computeAveragePosition(MotionEvent event, float[] position) { + computeAveragePosition(event, position, -1); + } + + private void computeAveragePosition(MotionEvent event, float[] position, int ignore) { + final int pointerCount = event.getPointerCount(); + position[0] = 0f; + position[1] = 0f; + float count = 0f; + for (int p = 0; p < pointerCount; p++) { + if (p != ignore) { + position[0] += event.getX(p); + position[1] += event.getY(p); + count += 1f; + } + } + position[0] /= count; + position[1] /= count; + } + + public boolean onTouchEvent(MotionEvent event) { + int index = event.getActionIndex(); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + computeAveragePosition(event, mLast); + mDrag = false; + break; + + case MotionEvent.ACTION_POINTER_DOWN: + mDrag = mTable.hasFocus(); + computeAveragePosition(event, mLast); + break; + + case MotionEvent.ACTION_POINTER_UP: + computeAveragePosition(event, mLast, index); + break; + + case MotionEvent.ACTION_MOVE: + computeAveragePosition(event, mCurrent); + if (mDrag) { + move(event, false); + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mDrag) { + move(event, true); + } + mDrag = false; + break; + } + + if (mDrag) { + mTable.refreshFocus(); + } + + return mDrag; + } + + private void move(MotionEvent event, boolean drop) { + mTable.move(mTable.getFocus(), + mTouchGain * (mCurrent[0] - mLast[0]), + mTouchGain * (mCurrent[1] - mLast[1]), + drop); + mLast[0] = mCurrent[0]; + mLast[1] = mCurrent[1]; + } +} + diff --git a/src/com/android/dreams/phototable/EdgeSwipeDetector.java b/src/com/android/dreams/phototable/EdgeSwipeDetector.java new file mode 100644 index 0000000..e5ca23d --- /dev/null +++ b/src/com/android/dreams/phototable/EdgeSwipeDetector.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dreams.phototable; + +import android.content.Context; +import android.content.res.Resources; +import android.view.MotionEvent; + +/** + * Detect and dispatch edge events. + */ +public class EdgeSwipeDetector { + @SuppressWarnings("unused") + private static final String TAG = "EdgeSwipeDetector"; + private float mEdgeSwipeGutter; + private float mEdgeSwipeThreshold; + private boolean mEdgeSwipe; + + private final PhotoTable mTable; + + public EdgeSwipeDetector(Context context, PhotoTable table) { + mTable = table; + final Resources resources = context.getResources(); + mEdgeSwipeGutter = resources.getInteger(R.integer.table_edge_swipe_gutter) / 1000000f; + mEdgeSwipeThreshold = resources.getInteger(R.integer.table_edge_swipe_threshold) / 1000000f; + } + + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + float edgeGutter = event.getDevice().getMotionRange(MotionEvent.AXIS_X).getMax() + * mEdgeSwipeGutter; + if (event.getX() < edgeGutter) { + mEdgeSwipe = true; + return true; + } + break; + + case MotionEvent.ACTION_MOVE: + if (mEdgeSwipe) { + return true; + } + break; + + case MotionEvent.ACTION_UP: + if (mEdgeSwipe) { + mEdgeSwipe = false; + float enough = event.getDevice().getMotionRange(MotionEvent.AXIS_X).getMax() + * mEdgeSwipeThreshold; + if (event.getX() > enough) { + if (mTable.hasFocus()) { + mTable.fling(mTable.getFocus()); + } else if (mTable.hasSelection()) { + mTable.clearSelection(); + } + } + return true; + } + break; + } + return false; + } +} diff --git a/src/com/android/dreams/phototable/FlipperDream.java b/src/com/android/dreams/phototable/FlipperDream.java index 36d8c7b..b70c8d4 100644 --- a/src/com/android/dreams/phototable/FlipperDream.java +++ b/src/com/android/dreams/phototable/FlipperDream.java @@ -15,7 +15,6 @@ */ package com.android.dreams.phototable; -import android.content.SharedPreferences; import android.service.dreams.DreamService; /** diff --git a/src/com/android/dreams/phototable/FlipperDreamSettings.java b/src/com/android/dreams/phototable/FlipperDreamSettings.java index 1252846..464029e 100644 --- a/src/com/android/dreams/phototable/FlipperDreamSettings.java +++ b/src/com/android/dreams/phototable/FlipperDreamSettings.java @@ -15,12 +15,16 @@ */ package com.android.dreams.phototable; -import android.content.SharedPreferences; import android.app.ListActivity; +import android.content.SharedPreferences; +import android.database.DataSetObserver; import android.os.AsyncTask; +import android.os.AsyncTask.Status; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; -import android.widget.ListAdapter; import java.util.LinkedList; @@ -28,22 +32,38 @@ import java.util.LinkedList; * Settings panel for photo flipping dream. */ public class FlipperDreamSettings extends ListActivity { + @SuppressWarnings("unused") private static final String TAG = "FlipperDreamSettings"; public static final String PREFS_NAME = FlipperDream.TAG; + protected SharedPreferences mSettings; + private PhotoSourcePlexor mPhotoSource; - private ListAdapter mAdapter; - private SharedPreferences mSettings; + private SectionedAlbumDataAdapter mAdapter; + private MenuItem mSelectAll; + private AsyncTask<Void, Void, Void> mLoadingTask; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); - mSettings = getSharedPreferences(PREFS_NAME, 0); + init(); + } + + @Override + protected void onResume(){ + super.onResume(); + init(); + } + + protected void init() { mPhotoSource = new PhotoSourcePlexor(this, mSettings); setContentView(R.layout.settingslist); - - new AsyncTask<Void, Void, Void>() { + if (mLoadingTask != null && mLoadingTask.getStatus() != Status.FINISHED) { + mLoadingTask.cancel(true); + } + showApology(false); + mLoadingTask = new AsyncTask<Void, Void, Void>() { @Override public Void doInBackground(Void... unused) { mAdapter = new SectionedAlbumDataAdapter(FlipperDreamSettings.this, @@ -56,11 +76,61 @@ public class FlipperDreamSettings extends ListActivity { @Override public void onPostExecute(Void unused) { + mAdapter.registerDataSetObserver(new DataSetObserver () { + @Override + public void onChanged() { + updateActionItem(); + } + @Override + public void onInvalidated() { + updateActionItem(); + } + }); setListAdapter(mAdapter); - if (mAdapter.getCount() == 0) { - findViewById(android.R.id.empty).setVisibility(View.GONE); - } + getListView().setItemsCanFocus(true); + updateActionItem(); + showApology(mAdapter.getCount() == 0); } - }.execute(); + }; + mLoadingTask.execute(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.photodream_settings_menu, menu); + mSelectAll = menu.findItem(R.id.photodream_menu_all); + updateActionItem(); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.photodream_menu_all: + mAdapter.selectAll(!mAdapter.areAllSelected()); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void showApology(boolean apologize) { + View empty = findViewById(R.id.spinner); + View sorry = findViewById(R.id.sorry); + if (empty != null && sorry != null) { + empty.setVisibility(apologize ? View.GONE : View.VISIBLE); + sorry.setVisibility(apologize ? View.VISIBLE : View.GONE); + } + } + + private void updateActionItem() { + if (mAdapter != null && mSelectAll != null) { + if (mAdapter.areAllSelected()) { + mSelectAll.setTitle(R.string.photodream_select_none); + } else { + mSelectAll.setTitle(R.string.photodream_select_all); + } + } } } diff --git a/src/com/android/dreams/phototable/KeyboardInterpreter.java b/src/com/android/dreams/phototable/KeyboardInterpreter.java new file mode 100644 index 0000000..874599b --- /dev/null +++ b/src/com/android/dreams/phototable/KeyboardInterpreter.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dreams.phototable; + +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; + +/** + * Keyboard event dispatcher for Photo Table. + */ +public class KeyboardInterpreter { + private static final String TAG = "DPadInterpreter"; + private static final boolean DEBUG = false; + + private final PhotoTable mTable; + private final long mBounce; + private long mLastDeckNavigation; + + public KeyboardInterpreter(PhotoTable table) { + mBounce = 2000; // TODO: remove this once latencies in lower layers are removed. + mTable = table; + } + public boolean onKeyDown(int keyCode, KeyEvent event) { + final View focus = mTable.getFocus(); + boolean consumed = true; + if (mTable.hasSelection()) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ESCAPE: + mTable.setFocus(mTable.getSelection()); + mTable.clearSelection(); + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_L: + if ((System.currentTimeMillis() - mLastDeckNavigation) > mBounce) { + mLastDeckNavigation = System.currentTimeMillis(); + mTable.selectPrevious(); + } + break; + + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_H: + if ((System.currentTimeMillis() - mLastDeckNavigation) > mBounce) { + mLastDeckNavigation = System.currentTimeMillis(); + mTable.selectNext(); + } + break; + + default: + if (DEBUG) Log.d(TAG, "dropped unexpected: " + keyCode); + consumed = false; + // give the user some more time to figure it out + mTable.refreshSelection(); + break; + } + } else { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (mTable.hasFocus()) { + mTable.setSelection(mTable.getFocus()); + mTable.clearFocus(); + } else { + mTable.setDefaultFocus(); + } + break; + + case KeyEvent.KEYCODE_DEL: + case KeyEvent.KEYCODE_X: + if (mTable.hasFocus()) { + mTable.fling(mTable.getFocus()); + } + break; + + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_K: + mTable.moveFocus(focus, 0f); + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_L: + mTable.moveFocus(focus, 90f); + break; + + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_J: + mTable.moveFocus(focus, 180f); + break; + + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_H: + mTable.moveFocus(focus, 270f); + break; + + default: + if (DEBUG) Log.d(TAG, "dropped unexpected: " + keyCode); + consumed = false; + // give the user some more time to figure it out + mTable.refreshFocus(); + break; + } + } + + return consumed; + } +} diff --git a/src/com/android/dreams/phototable/LocalSource.java b/src/com/android/dreams/phototable/LocalSource.java index 1faf589..cf2e0ec 100644 --- a/src/com/android/dreams/phototable/LocalSource.java +++ b/src/com/android/dreams/phototable/LocalSource.java @@ -30,20 +30,20 @@ import java.util.Set; /** * Loads images from the local store. */ -public class LocalSource extends PhotoSource { +public class LocalSource extends CursorPhotoSource { private static final String TAG = "PhotoTable.LocalSource"; private final String mUnknownAlbumName; private final String mLocalSourceName; private Set<String> mFoundAlbumIds; - private int mNextPosition; + private int mLastPosition; public LocalSource(Context context, SharedPreferences settings) { super(context, settings); mLocalSourceName = mResources.getString(R.string.local_source_name, "Photos on Device"); mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown"); mSourceName = TAG; - mNextPosition = -1; + mLastPosition = INVALID; fillQueue(); } @@ -65,7 +65,7 @@ public class LocalSource extends PhotoSource { Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, null); if (cursor != null) { - cursor.moveToFirst(); + cursor.moveToPosition(-1); int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); @@ -75,7 +75,7 @@ public class LocalSource extends PhotoSource { if (bucketIndex < 0) { log(TAG, "can't find the ID column!"); } else { - while (!cursor.isAfterLast()) { + while (cursor.moveToNext()) { String id = TAG + ":" + cursor.getString(bucketIndex); AlbumData data = foundAlbums.get(id); if (foundAlbums.get(id) == null) { @@ -102,7 +102,6 @@ public class LocalSource extends PhotoSource { updated : Math.min(data.updated, updated)); } - cursor.moveToNext(); } } cursor.close(); @@ -114,6 +113,59 @@ public class LocalSource extends PhotoSource { } @Override + protected void openCursor(ImageData data) { + log(TAG, "opening single album"); + + String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION, + MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + String selection = MediaStore.Images.Media.BUCKET_ID + " = '" + data.albumId + "'"; + + data.cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, selection, null, null); + } + + @Override + protected void findPosition(ImageData data) { + if (data.position == -1) { + if (data.cursor == null) { + openCursor(data); + } + if (data.cursor != null) { + int dataIndex = data.cursor.getColumnIndex(MediaStore.Images.Media.DATA); + data.cursor.moveToPosition(-1); + while (data.position == -1 && data.cursor.moveToNext()) { + String url = data.cursor.getString(dataIndex); + if (url != null && url.equals(data.url)) { + data.position = data.cursor.getPosition(); + } + } + if (data.position == -1) { + // oops! The image isn't in this album. How did we get here? + data.position = INVALID; + } + } + } + } + + @Override + protected ImageData unpackImageData(Cursor cursor, ImageData data) { + if (data == null) { + data = new ImageData(); + } + int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + int orientationIndex = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); + int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); + + data.url = cursor.getString(dataIndex); + data.albumId = cursor.getString(bucketIndex); + data.position = UNINITIALIZED; + data.cursor = null; + data.orientation = cursor.getInt(orientationIndex); + + return data; + } + + @Override protected Collection<ImageData> findImages(int howMany) { log(TAG, "finding images"); LinkedList<ImageData> foundImages = new LinkedList<ImageData>(); @@ -139,33 +191,26 @@ public class LocalSource extends PhotoSource { Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, null, null); if (cursor != null) { - if (cursor.getCount() > howMany && mNextPosition == -1) { - mNextPosition = mRNG.nextInt() % (cursor.getCount() - howMany); - } - if (mNextPosition == -1) { - mNextPosition = 0; - } - cursor.moveToPosition(mNextPosition); - int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); - int orientationIndex = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); - int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID); - int nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + + if (cursor.getCount() > howMany && mLastPosition == INVALID) { + mLastPosition = pickRandomStart(cursor.getCount(), howMany); + } + cursor.moveToPosition(mLastPosition); if (dataIndex < 0) { log(TAG, "can't find the DATA column!"); } else { - while (foundImages.size() < howMany && !cursor.isAfterLast()) { - ImageData data = new ImageData(); - data.url = cursor.getString(dataIndex); - data.orientation = cursor.getInt(orientationIndex); + while (foundImages.size() < howMany && cursor.moveToNext()) { + ImageData data = unpackImageData(cursor, null); foundImages.offer(data); - if (cursor.moveToNext()) { - mNextPosition++; - } + mLastPosition = cursor.getPosition(); } if (cursor.isAfterLast()) { - mNextPosition = 0; + mLastPosition = -1; + } + if (cursor.isBeforeFirst()) { + mLastPosition = INVALID; } } @@ -189,3 +234,4 @@ public class LocalSource extends PhotoSource { return (InputStream) fis; } } + diff --git a/src/com/android/dreams/phototable/PhotoCarousel.java b/src/com/android/dreams/phototable/PhotoCarousel.java index 70ba046..23939c9 100644 --- a/src/com/android/dreams/phototable/PhotoCarousel.java +++ b/src/com/android/dreams/phototable/PhotoCarousel.java @@ -20,7 +20,6 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; -import android.service.dreams.DreamService; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; @@ -80,7 +79,8 @@ public class PhotoCarousel extends FrameLayout { scheduleNext((int) mDropPeriod - elapsed); } else { scheduleNext(mDropPeriod); - if (changePhoto() || canFlip()) { + if (changePhoto() || + (elapsed > (5 * mDropPeriod) && canFlip())) { flip(1f); mLastFlipTime = now; } @@ -107,6 +107,7 @@ public class PhotoCarousel extends FrameLayout { mPanel = new View[2]; mFlipper = new Flipper(); + // this is dead code if the dream calls setInteractive(false) mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override @@ -183,22 +184,18 @@ public class PhotoCarousel extends FrameLayout { Bitmap photo = mBitmapQueue.poll(); if (photo != null) { ImageView destination = getBackface(); - Bitmap old = mBitmapStore.get(destination); int width = photo.getWidth(); int height = photo.getHeight(); int orientation = (width > height ? LANDSCAPE : PORTRAIT); destination.setImageBitmap(photo); - destination.setTag(R.id.photo_orientation, new Integer(orientation)); - destination.setTag(R.id.photo_width, new Integer(width)); - destination.setTag(R.id.photo_height, new Integer(height)); + destination.setTag(R.id.photo_orientation, Integer.valueOf(orientation)); + destination.setTag(R.id.photo_width, Integer.valueOf(width)); + destination.setTag(R.id.photo_height, Integer.valueOf(height)); setScaleType(destination); - mBitmapStore.put(destination, photo); - - if (old != null) { - old.recycle(); - } + Bitmap old = mBitmapStore.put(destination, photo); + mPhotoSource.recycle(old); return true; } else { @@ -247,8 +244,8 @@ public class PhotoCarousel extends FrameLayout { frontA = 1f - frontA; backA = 1f - backA; - // Don't rotate - frontY = backY = 0f; + // Don't rotate + frontY = backY = 0f; ViewPropertyAnimator frontAnim = mPanel[0].animate() .rotationY(frontY) @@ -284,7 +281,6 @@ public class PhotoCarousel extends FrameLayout { mOrientation = (mWidth > mHeight ? LANDSCAPE : PORTRAIT); - boolean init = mLongSide == 0; mLongSide = (int) Math.max(mWidth, mHeight); mShortSide = (int) Math.min(mWidth, mHeight); diff --git a/src/com/android/dreams/phototable/PhotoSource.java b/src/com/android/dreams/phototable/PhotoSource.java index 670bd02..fc4cf7b 100644 --- a/src/com/android/dreams/phototable/PhotoSource.java +++ b/src/com/android/dreams/phototable/PhotoSource.java @@ -23,16 +23,15 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; -import android.net.Uri; -import android.provider.MediaStore; import android.util.Log; +import java.io.BufferedInputStream; import java.io.FileNotFoundException; -import java.io.InputStream; import java.io.IOException; -import java.io.BufferedInputStream; +import java.io.InputStream; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.Random; @@ -52,9 +51,22 @@ public abstract class PhotoSource { public String url; public int orientation; + protected String albumId; + protected Cursor cursor; + protected int position; + InputStream getStream(int longSide) { return PhotoSource.this.getStream(this, longSide); } + ImageData naturalNext() { + return PhotoSource.this.naturalNext(this); + } + ImageData naturalPrevious() { + return PhotoSource.this.naturalPrevious(this); + } + public void donePaging() { + PhotoSource.this.donePaging(this); + } } public class AlbumData { @@ -76,6 +88,7 @@ public abstract class PhotoSource { private final float mMaxCropRatio; private final int mBadImageSkipLimit; private final PhotoSource mFallbackSource; + private final HashMap<Bitmap, ImageData> mImageMap; protected final Context mContext; protected final Resources mResources; @@ -99,6 +112,7 @@ public abstract class PhotoSource { mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size); mMaxCropRatio = mResources.getInteger(R.integer.max_crop_ratio) / 1000000f; mBadImageSkipLimit = mResources.getInteger(R.integer.bad_image_skip_limit); + mImageMap = new HashMap<Bitmap, ImageData>(); mRNG = new Random(); mFallbackSource = fallbackSource; } @@ -121,11 +135,11 @@ public abstract class PhotoSource { if (mImageQueue.isEmpty()) { fillQueue(); } - imageData = mImageQueue.poll(); } if (imageData != null) { image = load(imageData, options, longSide, shortSide); + mImageMap.put(image, imageData); imageData = null; } @@ -248,7 +262,61 @@ public abstract class PhotoSource { } } + protected int pickRandomStart(int total, int max) { + if (max >= total) { + return -1; + } else { + return (mRNG.nextInt() % (total - max)) - 1; + } + } + + public Bitmap naturalNext(Bitmap current, BitmapFactory.Options options, + int longSide, int shortSide) { + Bitmap image = null; + ImageData data = mImageMap.get(current); + if (data != null) { + ImageData next = data.naturalNext(); + if (next != null) { + image = load(next, options, longSide, shortSide); + mImageMap.put(image, next); + } + } + return image; + } + + public Bitmap naturalPrevious(Bitmap current, BitmapFactory.Options options, + int longSide, int shortSide) { + Bitmap image = null; + ImageData data = mImageMap.get(current); + if (current != null) { + ImageData prev = data.naturalPrevious(); + if (prev != null) { + image = load(prev, options, longSide, shortSide); + mImageMap.put(image, prev); + } + } + return image; + } + + public void donePaging(Bitmap current) { + ImageData data = mImageMap.get(current); + if (data != null) { + data.donePaging(); + } + } + + public void recycle(Bitmap trash) { + if (trash != null) { + mImageMap.remove(trash); + trash.recycle(); + } + } + protected abstract InputStream getStream(ImageData data, int longSide); protected abstract Collection<ImageData> findImages(int howMany); + protected abstract ImageData naturalNext(ImageData current); + protected abstract ImageData naturalPrevious(ImageData current); + protected abstract void donePaging(ImageData current); + public abstract Collection<AlbumData> findAlbums(); } diff --git a/src/com/android/dreams/phototable/PhotoSourcePlexor.java b/src/com/android/dreams/phototable/PhotoSourcePlexor.java index 147f16e..3733b02 100644 --- a/src/com/android/dreams/phototable/PhotoSourcePlexor.java +++ b/src/com/android/dreams/phototable/PhotoSourcePlexor.java @@ -17,12 +17,8 @@ package com.android.dreams.phototable; import android.content.Context; import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import java.io.FileNotFoundException; import java.io.InputStream; -import java.io.IOException; import java.util.Collection; import java.util.LinkedList; @@ -34,7 +30,6 @@ public class PhotoSourcePlexor extends PhotoSource { private final PhotoSource mPicasaSource; private final PhotoSource mLocalSource; - private SharedPreferences mSettings; public PhotoSourcePlexor(Context context, SharedPreferences settings) { super(context, settings); @@ -75,4 +70,19 @@ public class PhotoSourcePlexor extends PhotoSource { protected InputStream getStream(ImageData data, int longSide) { return data.getStream(longSide); } + + @Override + protected ImageData naturalNext(ImageData current) { + return current.naturalNext(); + } + + @Override + protected ImageData naturalPrevious(ImageData current) { + return current.naturalPrevious(); + } + + @Override + protected void donePaging(ImageData current) { + current.donePaging(); + } } diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java index 50212c9..7e7f92e 100644 --- a/src/com/android/dreams/phototable/PhotoTable.java +++ b/src/com/android/dreams/phototable/PhotoTable.java @@ -15,30 +15,38 @@ */ package com.android.dreams.phototable; -import android.service.dreams.DreamService; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.AsyncTask; +import android.service.dreams.DreamService; import android.util.AttributeSet; import android.util.Log; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; import android.widget.ImageView; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Random; +import java.util.Set; /** * A surface where photos sit. @@ -48,22 +56,34 @@ public class PhotoTable extends FrameLayout { private static final boolean DEBUG = false; class Launcher implements Runnable { - private final PhotoTable mTable; - public Launcher(PhotoTable table) { - mTable = table; + @Override + public void run() { + PhotoTable.this.scheduleNext(mDropPeriod); + PhotoTable.this.launch(); } + } + class FocusReaper implements Runnable { @Override public void run() { - mTable.scheduleNext(mDropPeriod); - mTable.launch(); + PhotoTable.this.clearFocus(); } } - private static final long MAX_SELECTION_TIME = 10000L; + class SelectionReaper implements Runnable { + @Override + public void run() { + PhotoTable.this.clearSelection(); + } + } + + private static final int NEXT = 1; + private static final int PREV = 0; private static Random sRNG = new Random(); private final Launcher mLauncher; + private final FocusReaper mFocusReaper; + private final SelectionReaper mSelectionReaper; private final LinkedList<View> mOnTable; private final int mDropPeriod; private final int mFastDropPeriod; @@ -77,20 +97,34 @@ public class PhotoTable extends FrameLayout { private final int mTableCapacity; private final int mRedealCount; private final int mInset; - private final PhotoSourcePlexor mPhotoSource; + private final PhotoSource mPhotoSource; private final Resources mResources; private final Interpolator mThrowInterpolator; private final Interpolator mDropInterpolator; + private final DragGestureDetector mDragGestureDetector; + private final EdgeSwipeDetector mEdgeSwipeDetector; + private final KeyboardInterpreter mKeyboardInterpreter; + private final boolean mStoryModeEnabled; + private final long mPickUpDuration; + private final int mMaxSelectionTime; + private final int mMaxFocusTime; + private final List<View> mAnimating; + private DreamService mDream; private PhotoLaunchTask mPhotoLaunchTask; + private LoadNaturalSiblingTask mLoadOnDeckTasks[]; private boolean mStarted; private boolean mIsLandscape; private int mLongSide; private int mShortSide; private int mWidth; private int mHeight; - private View mSelected; - private long mSelectedTime; + private View mSelection; + private View mOnDeck[]; + private View mFocus; + private int mHighlightColor; + private ViewGroup mBackground; + private ViewGroup mStageLeft; public PhotoTable(Context context, AttributeSet as) { super(context, as); @@ -103,10 +137,15 @@ public class PhotoTable extends FrameLayout { mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f; mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation); mThrowSpeed = mResources.getDimension(R.dimen.image_throw_speed); + mPickUpDuration = mResources.getInteger(R.integer.photo_pickup_duration); mThrowRotation = (float) mResources.getInteger(R.integer.image_throw_rotatioan); mTableCapacity = mResources.getInteger(R.integer.table_capacity); mRedealCount = mResources.getInteger(R.integer.redeal_count); mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit); + mStoryModeEnabled = mResources.getBoolean(R.bool.enable_story_mode); + mHighlightColor = mResources.getColor(R.color.highlight_color); + mMaxSelectionTime = mResources.getInteger(R.integer.max_selection_time); + mMaxFocusTime = mResources.getInteger(R.integer.max_focus_time); mThrowInterpolator = new SoftLandingInterpolator( mResources.getInteger(R.integer.soft_landing_time) / 1000000f, mResources.getInteger(R.integer.soft_landing_distance) / 1000000f); @@ -115,36 +154,145 @@ public class PhotoTable extends FrameLayout { mOnTable = new LinkedList<View>(); mPhotoSource = new PhotoSourcePlexor(getContext(), getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0)); - mLauncher = new Launcher(this); + mAnimating = new ArrayList<View>(); + mLauncher = new Launcher(); + mFocusReaper = new FocusReaper(); + mSelectionReaper = new SelectionReaper(); + mDragGestureDetector = new DragGestureDetector(context, this); + mEdgeSwipeDetector = new EdgeSwipeDetector(context, this); + mKeyboardInterpreter = new KeyboardInterpreter(this); + mLoadOnDeckTasks = new LoadNaturalSiblingTask[2]; + mOnDeck = new View[2]; mStarted = false; } - + @Override + public void onFinishInflate() { + mBackground = (ViewGroup) findViewById(R.id.background); + mStageLeft = (ViewGroup) findViewById(R.id.stageleft); + } + public void setDream(DreamService dream) { mDream = dream; } public boolean hasSelection() { - return mSelected != null; + return mSelection != null; } - public View getSelected() { - return mSelected; + public View getSelection() { + return mSelection; } public void clearSelection() { - mSelected = null; + if (hasSelection()) { + dropOnTable(mSelection); + mPhotoSource.donePaging(getBitmap(mSelection)); + if (mStoryModeEnabled) { + fadeInBackground(mSelection); + } + mSelection = null; + } + for (int slot = 0; slot < mOnDeck.length; slot++) { + if (mOnDeck[slot] != null) { + fadeAway(mOnDeck[slot], false); + mOnDeck[slot] = null; + } + if (mLoadOnDeckTasks[slot] != null && + mLoadOnDeckTasks[slot].getStatus() != AsyncTask.Status.FINISHED) { + mLoadOnDeckTasks[slot].cancel(true); + mLoadOnDeckTasks[slot] = null; + } + } } public void setSelection(View selected) { - assert(selected != null); - if (mSelected != null) { - dropOnTable(mSelected); + if (selected != null) { + clearSelection(); + mSelection = selected; + promoteSelection(); + if (mStoryModeEnabled) { + fadeOutBackground(mSelection); + } } - mSelected = selected; - mSelectedTime = System.currentTimeMillis(); - bringChildToFront(selected); - pickUp(selected); + } + + public void selectNext() { + if (mStoryModeEnabled) { + log("selectNext"); + if (hasSelection() && mOnDeck[NEXT] != null) { + placeOnDeck(mSelection, PREV); + mSelection = mOnDeck[NEXT]; + mOnDeck[NEXT] = null; + promoteSelection(); + } + } else { + clearSelection(); + } + } + + public void selectPrevious() { + if (mStoryModeEnabled) { + log("selectPrevious"); + if (hasSelection() && mOnDeck[PREV] != null) { + placeOnDeck(mSelection, NEXT); + mSelection = mOnDeck[PREV]; + mOnDeck[PREV] = null; + promoteSelection(); + } + } else { + clearSelection(); + } + } + + private void promoteSelection() { + if (hasSelection()) { + scheduleSelectionReaper(mMaxSelectionTime); + mSelection.animate().cancel(); + mSelection.setAlpha(1f); + moveToTopOfPile(mSelection); + pickUp(mSelection); + if (mStoryModeEnabled) { + for (int slot = 0; slot < mOnDeck.length; slot++) { + if (mLoadOnDeckTasks[slot] != null && + mLoadOnDeckTasks[slot].getStatus() != AsyncTask.Status.FINISHED) { + mLoadOnDeckTasks[slot].cancel(true); + } + if (mOnDeck[slot] == null) { + mLoadOnDeckTasks[slot] = new LoadNaturalSiblingTask(slot); + mLoadOnDeckTasks[slot].execute(mSelection); + } + } + } + } + } + + public boolean hasFocus() { + return mFocus != null; + } + + public View getFocus() { + return mFocus; + } + + public void clearFocus() { + if (hasFocus()) { + setHighlight(getFocus(), false); + } + mFocus = null; + } + + public void setDefaultFocus() { + setFocus(mOnTable.getLast()); + } + + public void setFocus(View focus) { + assert(focus != null); + clearFocus(); + mFocus = focus; + moveToTopOfPile(focus); + setHighlight(focus, true); + scheduleFocusReaper(mMaxFocusTime); } static float lerp(float a, float b, float f) { @@ -169,17 +317,8 @@ public class PhotoTable extends FrameLayout { return p; } - private static PointF randInCenter(float i, float j, int width, int height) { - log("randInCenter (" + i + ", " + j + ", " + width + ", " + height + ")"); - PointF p = new PointF(); - p.x = 0.5f * width + 0.15f * width * i; - p.y = 0.5f * height + 0.15f * height * j; - log("randInCenter returning " + p.x + "," + p.y); - return p; - } - private static PointF randMultiDrop(int n, float i, float j, int width, int height) { - log("randMultiDrop (" + n + "," + i + ", " + j + ", " + width + ", " + height + ")"); + log("randMultiDrop (%d, %f, %f, %d, %d)", n, i, j, width, height); final float[] cx = {0.3f, 0.3f, 0.5f, 0.7f, 0.7f}; final float[] cy = {0.3f, 0.7f, 0.5f, 0.3f, 0.7f}; n = Math.abs(n); @@ -188,15 +327,82 @@ public class PhotoTable extends FrameLayout { PointF p = new PointF(); p.x = x * width + 0.05f * width * i; p.y = y * height + 0.05f * height * j; - log("randInCenter returning " + p.x + "," + p.y); + log("randInCenter returning %f, %f", p.x, p.y); return p; } + private double cross(double[] a, double[] b) { + return a[0] * b[1] - a[1] * b[0]; + } + + private double norm(double[] a) { + return Math.hypot(a[0], a[1]); + } + + private double[] getCenter(View photo) { + float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue(); + float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue(); + double[] center = { photo.getX() + width / 2f, + - (photo.getY() + height / 2f) }; + return center; + } + + public View moveFocus(View focus, float direction) { + return moveFocus(focus, direction, 90f); + } + + public View moveFocus(View focus, float direction, float angle) { + if (focus == null) { + setFocus(mOnTable.getLast()); + } else { + final double alpha = Math.toRadians(direction); + final double beta = Math.toRadians(Math.min(angle, 180f) / 2f); + final double[] left = { Math.sin(alpha - beta), + Math.cos(alpha - beta) }; + final double[] right = { Math.sin(alpha + beta), + Math.cos(alpha + beta) }; + final double[] a = getCenter(focus); + View bestFocus = null; + double bestDistance = Double.MAX_VALUE; + for (View candidate: mOnTable) { + if (candidate != focus) { + final double[] b = getCenter(candidate); + final double[] delta = { b[0] - a[0], + b[1] - a[1] }; + if (cross(delta, left) > 0.0 && cross(delta, right) < 0.0) { + final double distance = norm(delta); + if (bestDistance > distance) { + bestDistance = distance; + bestFocus = candidate; + } + } + } + } + if (bestFocus == null) { + if (angle < 180f) { + return moveFocus(focus, direction, 180f); + } + } else { + setFocus(bestFocus); + } + } + return getFocus(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mKeyboardInterpreter.onKeyDown(keyCode, event); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return mEdgeSwipeDetector.onTouchEvent(event) || mDragGestureDetector.onTouchEvent(event); + } + @Override public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { if (hasSelection()) { - dropOnTable(getSelected()); clearSelection(); } else { if (mTapToExit && mDream != null) { @@ -211,7 +417,7 @@ public class PhotoTable extends FrameLayout { @Override public void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - log("onLayout (" + left + ", " + top + ", " + right + ", " + bottom + ")"); + log("onLayout (%d, %d, %d, %d)", left, top, right, bottom); mHeight = bottom - top; mWidth = right - left; @@ -222,12 +428,18 @@ public class PhotoTable extends FrameLayout { boolean isLandscape = mWidth > mHeight; if (mIsLandscape != isLandscape) { for (View photo: mOnTable) { - if (photo == getSelected()) { - pickUp(photo); - } else { + if (photo != getSelection()) { dropOnTable(photo); } } + if (hasSelection()) { + pickUp(getSelection()); + for (int slot = 0; slot < mOnDeck.length; slot++) { + if (mOnDeck[slot] != null) { + placeOnDeck(mOnDeck[slot], slot); + } + } + } mIsLandscape = isLandscape; } start(); @@ -238,47 +450,108 @@ public class PhotoTable extends FrameLayout { return true; } - private class PhotoLaunchTask extends AsyncTask<Void, Void, View> { + /** Put a nice border on the bitmap. */ + private static View applyFrame(final PhotoTable table, final BitmapFactory.Options options, + Bitmap decodedPhoto) { + LayoutInflater inflater = (LayoutInflater) table.getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View photo = inflater.inflate(R.layout.photo, null); + ImageView image = (ImageView) photo; + Drawable[] layers = new Drawable[2]; + int photoWidth = options.outWidth; + int photoHeight = options.outHeight; + if (decodedPhoto == null || options.outWidth <= 0 || options.outHeight <= 0) { + photo = null; + } else { + decodedPhoto.setHasMipMap(true); + layers[0] = new BitmapDrawable(table.mResources, decodedPhoto); + layers[1] = table.mResources.getDrawable(R.drawable.frame); + LayerDrawable layerList = new LayerDrawable(layers); + layerList.setLayerInset(0, table.mInset, table.mInset, + table.mInset, table.mInset); + image.setImageDrawable(layerList); + + photo.setTag(R.id.photo_width, Integer.valueOf(photoWidth)); + photo.setTag(R.id.photo_height, Integer.valueOf(photoHeight)); + + photo.setOnTouchListener(new PhotoTouchListener(table.getContext(), + table)); + } + return photo; + } + + private class LoadNaturalSiblingTask extends AsyncTask<View, Void, View> { private final BitmapFactory.Options mOptions; + private final int mSlot; + private View mParent; - public PhotoLaunchTask () { + public LoadNaturalSiblingTask (int slot) { mOptions = new BitmapFactory.Options(); mOptions.inTempStorage = new byte[32768]; + mSlot = slot; } @Override - public View doInBackground(Void... unused) { - log("load a new photo"); + public View doInBackground(View... views) { + log("load natural %s", (mSlot == NEXT ? "next" : "previous")); final PhotoTable table = PhotoTable.this; + mParent = views[0]; + final Bitmap current = getBitmap(mParent); + Bitmap decodedPhoto; + if (mSlot == NEXT) { + decodedPhoto = table.mPhotoSource.naturalNext(current, + mOptions, table.mLongSide, table.mShortSide); + } else { + decodedPhoto = table.mPhotoSource.naturalPrevious(current, + mOptions, table.mLongSide, table.mShortSide); + } + return applyFrame(PhotoTable.this, mOptions, decodedPhoto); + } - LayoutInflater inflater = (LayoutInflater) table.getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View photo = inflater.inflate(R.layout.photo, null); - ImageView image = (ImageView) photo; - Drawable[] layers = new Drawable[2]; - Bitmap decodedPhoto = table.mPhotoSource.next(mOptions, - table.mLongSide, table.mShortSide); - int photoWidth = mOptions.outWidth; - int photoHeight = mOptions.outHeight; - if (mOptions.outWidth <= 0 || mOptions.outHeight <= 0) { - photo = null; + @Override + public void onPostExecute(View photo) { + if (photo != null) { + if (hasSelection() && getSelection() == mParent) { + log("natural %s being rendered", (mSlot == NEXT ? "next" : "previous")); + PhotoTable.this.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + PhotoTable.this.mOnDeck[mSlot] = photo; + float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue(); + float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue(); + photo.setX(mSlot == PREV ? -2 * width : mWidth + 2 * width); + photo.setY((mHeight - height) / 2); + photo.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + PhotoTable.this.placeOnDeck(v, mSlot); + v.removeOnLayoutChangeListener(this); + } + }); + } else { + recycle(photo); + } } else { - decodedPhoto.setHasMipMap(true); - layers[0] = new BitmapDrawable(table.mResources, decodedPhoto); - layers[1] = table.mResources.getDrawable(R.drawable.frame); - LayerDrawable layerList = new LayerDrawable(layers); - layerList.setLayerInset(0, table.mInset, table.mInset, - table.mInset, table.mInset); - image.setImageDrawable(layerList); - - photo.setTag(R.id.photo_width, new Integer(photoWidth)); - photo.setTag(R.id.photo_height, new Integer(photoHeight)); - - photo.setOnTouchListener(new PhotoTouchListener(table.getContext(), - table)); + log("natural, %s was null!", (mSlot == NEXT ? "next" : "previous")); } + } + }; + + private class PhotoLaunchTask extends AsyncTask<Void, Void, View> { + private final BitmapFactory.Options mOptions; + + public PhotoLaunchTask () { + mOptions = new BitmapFactory.Options(); + mOptions.inTempStorage = new byte[32768]; + } - return photo; + @Override + public View doInBackground(Void... unused) { + log("load a new photo"); + final PhotoTable table = PhotoTable.this; + return applyFrame(PhotoTable.this, mOptions, + table.mPhotoSource.next(mOptions, + table.mLongSide, table.mShortSide)); } @Override @@ -287,16 +560,26 @@ public class PhotoTable extends FrameLayout { final PhotoTable table = PhotoTable.this; table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT)); + LayoutParams.WRAP_CONTENT)); if (table.hasSelection()) { - table.bringChildToFront(table.getSelected()); + for (int slot = 0; slot < mOnDeck.length; slot++) { + if (mOnDeck[slot] != null) { + table.moveToTopOfPile(mOnDeck[slot]); + } + } + table.moveToTopOfPile(table.getSelection()); } - int width = ((Integer) photo.getTag(R.id.photo_width)).intValue(); - int height = ((Integer) photo.getTag(R.id.photo_height)).intValue(); log("drop it"); table.throwOnTable(photo); + if (mOnTable.size() > mTableCapacity) { + int targetSize = Math.max(0, mOnTable.size() - mRedealCount); + while (mOnTable.size() > targetSize) { + fadeAway(mOnTable.poll(), false); + } + } + if(table.mOnTable.size() < table.mTableCapacity) { table.scheduleNext(table.mFastDropPeriod); } @@ -304,14 +587,11 @@ public class PhotoTable extends FrameLayout { } }; + /** Bring a new photo onto the table. */ public void launch() { log("launching"); - setSystemUiVisibility(View.STATUS_BAR_HIDDEN); - if (hasSelection() && - (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) { - dropOnTable(getSelected()); - clearSelection(); - } else { + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + if (!hasSelection()) { log("inflate it"); if (mPhotoLaunchTask == null || mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) { @@ -320,18 +600,52 @@ public class PhotoTable extends FrameLayout { } } } + + /** De-emphasize the other photos on the table. */ + public void fadeOutBackground(final View photo) { + mBackground.animate() + .withLayer() + .setDuration(mPickUpDuration) + .alpha(0f); + } + + + /** Return the other photos to foreground status. */ + public void fadeInBackground(final View photo) { + mAnimating.add(photo); + mBackground.animate() + .withLayer() + .setDuration(mPickUpDuration) + .alpha(1f) + .withEndAction(new Runnable() { + @Override + public void run() { + mAnimating.remove(photo); + if (!mAnimating.contains(photo)) { + moveToBackground(photo); + } + } + }); + } + + /** Dispose of the photo gracefully, in case we can see some of it. */ public void fadeAway(final View photo, final boolean replace) { // fade out of view mOnTable.remove(photo); + exitStageLeft(photo); + photo.setOnTouchListener(null); photo.animate().cancel(); photo.animate() .withLayer() .alpha(0f) - .setDuration(1000) + .setDuration(mPickUpDuration) .withEndAction(new Runnable() { @Override public void run() { - removeView(photo); + if (photo == getFocus()) { + clearFocus(); + } + mStageLeft.removeView(photo); recycle(photo); if (replace) { scheduleNext(mNowDropDelay); @@ -340,19 +654,62 @@ public class PhotoTable extends FrameLayout { }); } - public void moveToBackOfQueue(View photo) { + /** Visually on top, and also freshest, for the purposes of timeouts. */ + public void moveToTopOfPile(View photo) { // make this photo the last to be removed. - bringChildToFront(photo); + if (isInBackground(photo)) { + mBackground.bringChildToFront(photo); + } else { + bringChildToFront(photo); + } invalidate(); mOnTable.remove(photo); mOnTable.offer(photo); } + /** On deck is to the left or right of the selected photo. */ + private void placeOnDeck(final View photo, final int slot ) { + if (slot < mOnDeck.length) { + if (mOnDeck[slot] != null && mOnDeck[slot] != photo) { + fadeAway(mOnDeck[slot], false); + } + mOnDeck[slot] = photo; + float photoWidth = photo.getWidth(); + float photoHeight = photo.getHeight(); + float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth); + + float x = (getWidth() - photoWidth) / 2f; + float y = (getHeight() - photoHeight) / 2f; + + float offset = (((float) mWidth + scale * (photoWidth - 2f * mInset)) / 2f); + x += (slot == NEXT? 1f : -1f) * offset; + + photo.animate() + .withLayer() + .rotation(0f) + .rotationY(0f) + .scaleX(scale) + .scaleY(scale) + .x(x) + .y(y) + .setDuration(mPickUpDuration) + .setInterpolator(new DecelerateInterpolator(2f)); + } + } + + /** Move in response to touch. */ + public void move(final View photo, float x, float y, float a) { + photo.animate().cancel(); + photo.setAlpha(1f); + photo.setX((int) x); + photo.setY((int) y); + photo.setRotation((int) a); + } + + /** Wind up off screen, so we can animate in. */ private void throwOnTable(final View photo) { mOnTable.offer(photo); log("start offscreen"); - int width = ((Integer) photo.getTag(R.id.photo_width)); - int height = ((Integer) photo.getTag(R.id.photo_height)); photo.setRotation(mThrowRotation); photo.setX(-mLongSide); photo.setY(-mLongSide); @@ -360,10 +717,87 @@ public class PhotoTable extends FrameLayout { dropOnTable(photo, mThrowInterpolator); } + public void move(final View photo, float dx, float dy, boolean drop) { + if (photo != null) { + final float x = photo.getX() + dx; + final float y = photo.getY() + dy; + photo.setX(x); + photo.setY(y); + Log.d(TAG, "[" + photo.getX() + ", " + photo.getY() + "] + (" + dx + "," + dy + ")"); + if (drop && photoOffTable(photo)) { + fadeAway(photo, true); + } + } + } + + /** Fling with no touch hints, then land off screen. */ + public void fling(final View photo) { + final float[] o = { mWidth + mLongSide / 2f, + mHeight + mLongSide / 2f }; + final float[] a = { photo.getX(), photo.getY() }; + final float[] b = { o[0], a[1] + o[0] - a[0] }; + final float[] c = { a[0] + o[1] - a[1], o[1] }; + float[] delta = { 0f, 0f }; + if (Math.hypot(b[0] - a[0], b[1] - a[1]) < Math.hypot(c[0] - a[0], c[1] - a[1])) { + delta[0] = b[0] - a[0]; + delta[1] = b[1] - a[1]; + } else { + delta[0] = c[0] - a[0]; + delta[1] = c[1] - a[1]; + } + + final float dist = (float) Math.hypot(delta[0], delta[1]); + final int duration = (int) (1000f * dist / mThrowSpeed); + fling(photo, delta[0], delta[1], duration, true); + } + + /** Continue dynamically after a fling gesture, possibly off the screen. */ + public void fling(final View photo, float dx, float dy, int duration, boolean spin) { + if (photo == getFocus()) { + if (moveFocus(photo, 0f) == null) { + moveFocus(photo, 180f); + } + } + moveToForeground(photo); + ViewPropertyAnimator animator = photo.animate() + .withLayer() + .xBy(dx) + .yBy(dy) + .setDuration(duration) + .setInterpolator(new DecelerateInterpolator(2f)); + + if (spin) { + animator.rotation(mThrowRotation); + } + + if (photoOffTable(photo, (int) dx, (int) dy)) { + log("fling away"); + animator.withEndAction(new Runnable() { + @Override + public void run() { + fadeAway(photo, true); + } + }); + } + } + public boolean photoOffTable(final View photo) { + return photoOffTable(photo, 0, 0); + } + + public boolean photoOffTable(final View photo, final int dx, final int dy) { + Rect hit = new Rect(); + photo.getHitRect(hit); + hit.offset(dx, dy); + return (hit.bottom < 0f || hit.top > getHeight() || + hit.right < 0f || hit.left > getWidth()); + } + + /** Animate to a random place and orientation, down on the table (visually small). */ public void dropOnTable(final View photo) { dropOnTable(photo, mDropInterpolator); } + /** Animate to a random place and orientation, down on the table (visually small). */ public void dropOnTable(final View photo, final Interpolator interpolator) { float angle = randfrange(-mImageRotationLimit, mImageRotationLimit); PointF p = randMultiDrop(sRNG.nextInt(), @@ -372,46 +806,73 @@ public class PhotoTable extends FrameLayout { float x = p.x; float y = p.y; - log("drop it at " + x + ", " + y); + log("drop it at %f, %f", x, y); float x0 = photo.getX(); float y0 = photo.getY(); - float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue(); - float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue(); x -= mLongSide / 2f; y -= mShortSide / 2f; - log("fixed offset is " + x + ", " + y); + log("fixed offset is %f, %f ", x, y); float dx = x - x0; float dy = y - y0; - float dist = (float) (Math.sqrt(dx * dx + dy * dy)); + float dist = (float) Math.hypot(dx, dy); int duration = (int) (1000f * dist / mThrowSpeed); duration = Math.max(duration, 1000); log("animate it"); // toss onto table + mAnimating.add(photo); photo.animate() - .scaleX(mTableRatio / mImageRatio) - .scaleY(mTableRatio / mImageRatio) - .rotation(angle) - .x(x) - .y(y) - .setDuration(duration) - .setInterpolator(interpolator) - .withEndAction(new Runnable() { - @Override - public void run() { - if (mOnTable.size() > mTableCapacity) { - while (mOnTable.size() > (mTableCapacity - mRedealCount)) { - fadeAway(mOnTable.poll(), false); - } - // zero delay because we already waited duration ms - scheduleNext(0); - } - } - }); + .withLayer() + .scaleX(mTableRatio / mImageRatio) + .scaleY(mTableRatio / mImageRatio) + .rotation(angle) + .x(x) + .y(y) + .setDuration(duration) + .setInterpolator(interpolator) + .withEndAction(new Runnable() { + @Override + public void run() { + mAnimating.remove(photo); + if (!mAnimating.contains(photo)) { + moveToBackground(photo); + } + } + }); + } + + private void moveToBackground(View photo) { + if (!isInBackground(photo)) { + removeView(photo); + mBackground.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + } + } + + private void exitStageLeft(View photo) { + if (isInBackground(photo)) { + mBackground.removeView(photo); + } else { + removeView(photo); + } + mStageLeft.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + } + + private void moveToForeground(View photo) { + if (isInBackground(photo)) { + mBackground.removeView(photo); + addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + } + } + + private boolean isInBackground(View photo) { + return mBackground.indexOfChild(photo) != -1; } /** wrap all orientations to the interval [-180, 180). */ @@ -422,69 +883,112 @@ public class PhotoTable extends FrameLayout { return result; } + /** Animate the selected photo to the foregound: zooming in to bring it foreward. */ private void pickUp(final View photo) { float photoWidth = photo.getWidth(); float photoHeight = photo.getHeight(); float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth); + log("scale is %f", scale); log("target it"); float x = (getWidth() - photoWidth) / 2f; float y = (getHeight() - photoHeight) / 2f; - float x0 = photo.getX(); - float y0 = photo.getY(); - float dx = x - x0; - float dy = y - y0; - - float dist = (float) (Math.sqrt(dx * dx + dy * dy)); - int duration = (int) (1000f * dist / 600f); - duration = Math.max(duration, 500); - photo.setRotation(wrapAngle(photo.getRotation())); log("animate it"); - // toss onto table + // lift up to the glass for a good look + moveToForeground(photo); photo.animate() - .rotation(0f) - .scaleX(scale) - .scaleY(scale) - .x(x) - .y(y) - .setDuration(duration) - .setInterpolator(new DecelerateInterpolator(2f)) - .withEndAction(new Runnable() { - @Override - public void run() { - log("endtimes: " + photo.getX()); - } - }); + .withLayer() + .rotation(0f) + .rotationY(0f) + .alpha(1f) + .scaleX(scale) + .scaleY(scale) + .x(x) + .y(y) + .setDuration(mPickUpDuration) + .setInterpolator(new DecelerateInterpolator(2f)) + .withEndAction(new Runnable() { + @Override + public void run() { + log("endtimes: %f", photo.getX()); + } + }); } - private void recycle(View photo) { + private Bitmap getBitmap(View photo) { + if (photo == null) { + return null; + } ImageView image = (ImageView) photo; LayerDrawable layers = (LayerDrawable) image.getDrawable(); + if (layers == null) { + return null; + } BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0); - bitmap.getBitmap().recycle(); + if (bitmap == null) { + return null; + } + return bitmap.getBitmap(); + } + + private void recycle(View photo) { + if (photo != null) { + removeView(photo); + mPhotoSource.recycle(getBitmap(photo)); + } } + public void setHighlight(View photo, boolean highlighted) { + ImageView image = (ImageView) photo; + LayerDrawable layers = (LayerDrawable) image.getDrawable(); + if (highlighted) { + layers.getDrawable(1).setColorFilter(mHighlightColor, PorterDuff.Mode.SRC_IN); + } else { + layers.getDrawable(1).clearColorFilter(); + } + } + + /** Schedule the first launch. Idempotent. */ public void start() { if (!mStarted) { log("kick it"); mStarted = true; - scheduleNext(mDropPeriod); - launch(); + scheduleNext(0); } } + public void refreshSelection() { + scheduleSelectionReaper(mMaxFocusTime); + } + + public void scheduleSelectionReaper(int delay) { + removeCallbacks(mSelectionReaper); + postDelayed(mSelectionReaper, delay); + } + + public void refreshFocus() { + scheduleFocusReaper(mMaxFocusTime); + } + + public void scheduleFocusReaper(int delay) { + removeCallbacks(mFocusReaper); + postDelayed(mFocusReaper, delay); + } + public void scheduleNext(int delay) { removeCallbacks(mLauncher); postDelayed(mLauncher, delay); } - private static void log(String message) { + private static void log(String message, Object... args) { if (DEBUG) { - Log.i(TAG, message); + Formatter formatter = new Formatter(); + formatter.format(message, args); + Log.i(TAG, formatter.toString()); } } } diff --git a/src/com/android/dreams/phototable/PhotoTableDream.java b/src/com/android/dreams/phototable/PhotoTableDream.java index 37ea019..dd23be2 100644 --- a/src/com/android/dreams/phototable/PhotoTableDream.java +++ b/src/com/android/dreams/phototable/PhotoTableDream.java @@ -15,20 +15,14 @@ */ package com.android.dreams.phototable; -import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Resources; import android.service.dreams.DreamService; -import java.util.Set; - /** * Example interactive screen saver: flick photos onto a table. */ public class PhotoTableDream extends DreamService { public static final String TAG = "PhotoTableDream"; - private PhotoTable mTable; - @Override public void onDreamingStarted() { super.onDreamingStarted(); diff --git a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java index 6f7e9f1..7ae8df3 100644 --- a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java +++ b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java @@ -15,48 +15,20 @@ */ package com.android.dreams.phototable; -import android.content.SharedPreferences; -import android.app.ListActivity; -import android.os.AsyncTask; import android.os.Bundle; -import android.widget.ListAdapter; - -import java.util.LinkedList; /** * Settings panel for photo flipping dream. */ -public class PhotoTableDreamSettings extends ListActivity { +public class PhotoTableDreamSettings extends FlipperDreamSettings { + @SuppressWarnings("unused") private static final String TAG = "PhotoTableDreamSettings"; public static final String PREFS_NAME = PhotoTableDream.TAG; - private PhotoSourcePlexor mPhotoSource; - private ListAdapter mAdapter; - private SharedPreferences mSettings; - @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); - mSettings = getSharedPreferences(PREFS_NAME, 0); - mPhotoSource = new PhotoSourcePlexor(this, mSettings); - setContentView(R.layout.settingslist); - - new AsyncTask<Void, Void, Void>() { - @Override - public Void doInBackground(Void... unused) { - mAdapter = new SectionedAlbumDataAdapter(PhotoTableDreamSettings.this, - mSettings, - R.layout.header, - R.layout.album, - new LinkedList<PhotoSource.AlbumData>(mPhotoSource.findAlbums())); - return null; - } - - @Override - public void onPostExecute(Void unused) { - setListAdapter(mAdapter); - } - }.execute(); + init(); } } diff --git a/src/com/android/dreams/phototable/PhotoTouchListener.java b/src/com/android/dreams/phototable/PhotoTouchListener.java index 8076e72..8bcec6b 100644 --- a/src/com/android/dreams/phototable/PhotoTouchListener.java +++ b/src/com/android/dreams/phototable/PhotoTouchListener.java @@ -21,8 +21,6 @@ import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewPropertyAnimator; -import android.view.animation.DecelerateInterpolator; /** * Touch listener that implements phototable interactions. @@ -36,7 +34,6 @@ public class PhotoTouchListener implements View.OnTouchListener { private final int mTapTimeout; private final PhotoTable mTable; private final float mBeta; - private final float mTableRatio; private final boolean mEnableFling; private final boolean mManualImageRotation; private long mLastEventTime; @@ -54,16 +51,14 @@ public class PhotoTouchListener implements View.OnTouchListener { private int mA = INVALID_POINTER; private int mB = INVALID_POINTER; private float[] pts = new float[MAX_POINTER_COUNT]; - private float[] tmp = new float[MAX_POINTER_COUNT]; public PhotoTouchListener(Context context, PhotoTable table) { mTable = table; final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); - mTapTimeout = configuration.getTapTimeout(); + mTapTimeout = ViewConfiguration.getTapTimeout(); final Resources resources = context.getResources(); mBeta = resources.getInteger(R.integer.table_damping) / 1000000f; - mTableRatio = resources.getInteger(R.integer.table_ratio) / 1000000f; mEnableFling = resources.getBoolean(R.bool.enable_fling); mManualImageRotation = resources.getBoolean(R.bool.enable_manual_image_rotation); } @@ -120,29 +115,7 @@ public class PhotoTouchListener implements View.OnTouchListener { final float x1 = x0 + s * dX / v; final float y1 = y0 + s * dY / v; - final float photoWidth = ((Integer) target.getTag(R.id.photo_width)).floatValue(); - final float photoHeight = ((Integer) target.getTag(R.id.photo_height)).floatValue(); - final float tableWidth = mTable.getWidth(); - final float tableHeight = mTable.getHeight(); - final float halfShortSide = - Math.min(photoWidth * mTableRatio, photoHeight * mTableRatio) / 2f; - final View photo = target; - ViewPropertyAnimator animator = photo.animate() - .xBy(x1 - x0) - .yBy(y1 - y0) - .setDuration((int) (1000f * n / 60f)) - .setInterpolator(new DecelerateInterpolator(2f)); - - if (y1 + halfShortSide < 0f || y1 - halfShortSide > tableHeight || - x1 + halfShortSide < 0f || x1 - halfShortSide > tableWidth) { - log("fling away"); - animator.withEndAction(new Runnable() { - @Override - public void run() { - mTable.fadeAway(photo, true); - } - }); - } + mTable.fling(target, x1 - x0, y1 - y0, (int) (1000f * n / 60f), false); } @Override @@ -158,7 +131,7 @@ public class PhotoTouchListener implements View.OnTouchListener { switch (action) { case MotionEvent.ACTION_DOWN: - mTable.moveToBackOfQueue(target); + mTable.moveToTopOfPile(target); mInitialTouchTime = ev.getEventTime(); mA = ev.getPointerId(ev.getActionIndex()); resetTouch(target); @@ -208,16 +181,16 @@ public class PhotoTouchListener implements View.OnTouchListener { mLastTouchY = y; } - if (mTable.getSelected() != target) { - target.animate().cancel(); - - target.setX((int) (mInitialTargetX + x - mInitialTouchX)); - target.setY((int) (mInitialTargetY + y - mInitialTouchY)); + if (!mTable.hasSelection()) { + float rotation = target.getRotation(); if (mManualImageRotation && mB != INVALID_POINTER) { float a = getAngle(target, ev); - target.setRotation( - (int) (mInitialTargetA + a - mInitialTouchA)); + rotation = mInitialTargetA + a - mInitialTouchA; } + mTable.move(target, + mInitialTargetX + x - mInitialTouchX, + mInitialTargetY + y - mInitialTouchY, + rotation); } } } @@ -234,13 +207,19 @@ public class PhotoTouchListener implements View.OnTouchListener { } double distance = Math.hypot(x0 - mInitialTouchX, y0 - mInitialTouchY); - if (mTable.getSelected() == target) { - mTable.dropOnTable(target); - mTable.clearSelection(); + if (mTable.hasSelection()) { + if (distance < mTouchSlop) { + mTable.clearSelection(); + } else { + if ((x0 - mInitialTouchX) > 0f) { + mTable.selectPrevious(); + } else { + mTable.selectNext(); + } + } } else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout && distance < mTouchSlop) { // tap - target.animate().cancel(); mTable.setSelection(target); } else { onFling(target, mDX, mDY); diff --git a/src/com/android/dreams/phototable/PicasaSource.java b/src/com/android/dreams/phototable/PicasaSource.java index 9513d3c..0db98af 100644 --- a/src/com/android/dreams/phototable/PicasaSource.java +++ b/src/com/android/dreams/phototable/PicasaSource.java @@ -26,7 +26,6 @@ import android.view.WindowManager; import java.io.FileNotFoundException; import java.io.InputStream; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -36,7 +35,7 @@ import java.util.Set; /** * Loads images from Picasa. */ -public class PicasaSource extends PhotoSource { +public class PicasaSource extends CursorPhotoSource { private static final String TAG = "PhotoTable.PicasaSource"; private static final String PICASA_AUTHORITY = @@ -61,10 +60,10 @@ public class PicasaSource extends PhotoSource { private static final String PICASA_TYPE_KEY = "type"; private static final String PICASA_TYPE_FULL_VALUE = "full"; private static final String PICASA_TYPE_SCREEN_VALUE = "screennail"; - private static final String PICASA_TYPE_THUMB_VALUE = "thumbnail"; private static final String PICASA_TYPE_IMAGE_VALUE = "image"; private static final String PICASA_POSTS_TYPE = "Buzz"; private static final String PICASA_UPLOAD_TYPE = "InstantUpload"; + private static final String PICASA_UPLOADAUTO_TYPE = "InstantUploadAuto"; private final int mMaxPostAblums; private final String mPostsAlbumName; @@ -75,13 +74,13 @@ public class PicasaSource extends PhotoSource { private final int mMaxRecycleSize; private Set<String> mFoundAlbumIds; - private int mNextPosition; + private int mLastPosition; private int mDisplayLongSide; public PicasaSource(Context context, SharedPreferences settings) { super(context, settings); mSourceName = TAG; - mNextPosition = -1; + mLastPosition = INVALID; mMaxPostAblums = mResources.getInteger(R.integer.max_post_albums); mPostsAlbumName = mResources.getString(R.string.posts_album_name, "Posts"); mUploadsAlbumName = mResources.getString(R.string.uploads_album_name, "Instant Uploads"); @@ -90,6 +89,7 @@ public class PicasaSource extends PhotoSource { mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mRecycleBin = new LinkedList<ImageData>(); + fillQueue(); mDisplayLongSide = getDisplayLongSide(); } @@ -103,6 +103,65 @@ public class PicasaSource extends PhotoSource { } @Override + protected void openCursor(ImageData data) { + log(TAG, "opening single album"); + + String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID}; + String selection = PICASA_ALBUM_ID + " = '" + data.albumId + "'"; + + Uri.Builder picasaUriBuilder = new Uri.Builder() + .scheme("content") + .authority(PICASA_AUTHORITY) + .appendPath(PICASA_PHOTO_PATH); + data.cursor = mResolver.query(picasaUriBuilder.build(), + projection, selection, null, null); + } + + @Override + protected void findPosition(ImageData data) { + if (data.position == UNINITIALIZED) { + if (data.cursor == null) { + openCursor(data); + } + if (data.cursor != null) { + int idIndex = data.cursor.getColumnIndex(PICASA_ID); + data.cursor.moveToPosition(-1); + while (data.position == -1 && data.cursor.moveToNext()) { + String id = data.cursor.getString(idIndex); + if (id != null && id.equals(data.id)) { + data.position = data.cursor.getPosition(); + } + } + if (data.position == -1) { + // oops! The image isn't in this album. How did we get here? + data.position = INVALID; + } + } + } + } + + @Override + protected ImageData unpackImageData(Cursor cursor, ImageData data) { + if (data == null) { + data = new ImageData(); + } + int idIndex = cursor.getColumnIndex(PICASA_ID); + int urlIndex = cursor.getColumnIndex(PICASA_URL); + int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID); + + data.id = cursor.getString(idIndex); + if (bucketIndex >= 0) { + data.albumId = cursor.getString(bucketIndex); + } + if (urlIndex >= 0) { + data.url = cursor.getString(urlIndex); + } + data.position = UNINITIALIZED; + data.cursor = null; + return data; + } + + @Override protected Collection<ImageData> findImages(int howMany) { log(TAG, "finding images"); LinkedList<ImageData> foundImages = new LinkedList<ImageData>(); @@ -117,7 +176,6 @@ public class PicasaSource extends PhotoSource { } String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID}; - boolean usePosts = false; LinkedList<String> albumIds = new LinkedList<String>(); for (String id : getFoundAlbums()) { if (mSettings.isAlbumEnabled(id)) { @@ -162,41 +220,30 @@ public class PicasaSource extends PhotoSource { Cursor cursor = mResolver.query(picasaUriBuilder.build(), projection, selection.toString(), null, null); if (cursor != null) { - if (cursor.getCount() > howMany && mNextPosition == -1) { - mNextPosition = - (int) Math.abs(mRNG.nextInt() % (cursor.getCount() - howMany)); - } - if (mNextPosition == -1) { - mNextPosition = 0; + if (cursor.getCount() > howMany && mLastPosition == INVALID) { + mLastPosition = pickRandomStart(cursor.getCount(), howMany); } - log(TAG, "moving to position: " + mNextPosition); - cursor.moveToPosition(mNextPosition); + + log(TAG, "moving to position: " + mLastPosition); + cursor.moveToPosition(mLastPosition); int idIndex = cursor.getColumnIndex(PICASA_ID); - int urlIndex = cursor.getColumnIndex(PICASA_URL); - int orientationIndex = cursor.getColumnIndex(PICASA_ROTATION); - int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID); if (idIndex < 0) { log(TAG, "can't find the ID column!"); } else { - while (foundImages.size() < howMany && !cursor.isAfterLast()) { + while (cursor.moveToNext()) { if (idIndex >= 0) { - ImageData data = new ImageData(); - data.id = cursor.getString(idIndex); - - if (urlIndex >= 0) { - data.url = cursor.getString(urlIndex); - } - + ImageData data = unpackImageData(cursor, null); foundImages.offer(data); } - if (cursor.moveToNext()) { - mNextPosition++; - } + mLastPosition = cursor.getPosition(); } if (cursor.isAfterLast()) { - mNextPosition = 0; + mLastPosition = -1; + } + if (cursor.isBeforeFirst()) { + mLastPosition = INVALID; } } @@ -254,17 +301,15 @@ public class PicasaSource extends PhotoSource { projection, selection, null, order); if (cursor != null) { log(TAG, " " + id + " resolved to " + cursor.getCount() + " albums"); - cursor.moveToFirst(); + cursor.moveToPosition(-1); int idIndex = cursor.getColumnIndex(PICASA_ID); - int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE); if (idIndex < 0) { log(TAG, "can't find the ID column!"); } else { - while (!cursor.isAfterLast()) { + while (cursor.moveToNext()) { albumIds.add(cursor.getString(idIndex)); - cursor.moveToNext(); } } cursor.close(); @@ -296,7 +341,7 @@ public class PicasaSource extends PhotoSource { Cursor cursor = mResolver.query(picasaUriBuilder.build(), projection, null, null, null); if (cursor != null) { - cursor.moveToFirst(); + cursor.moveToPosition(-1); int idIndex = cursor.getColumnIndex(PICASA_ID); int thumbIndex = cursor.getColumnIndex(PICASA_THUMB); @@ -308,12 +353,13 @@ public class PicasaSource extends PhotoSource { if (idIndex < 0) { log(TAG, "can't find the ID column!"); } else { - while (!cursor.isAfterLast()) { + while (cursor.moveToNext()) { String id = TAG + ":" + cursor.getString(idIndex); String user = (userIndex >= 0 ? cursor.getString(userIndex) : "-1"); String type = (typeIndex >= 0 ? cursor.getString(typeIndex) : "none"); boolean isPosts = (typeIndex >= 0 && PICASA_POSTS_TYPE.equals(type)); - boolean isUpload = (typeIndex >= 0 && PICASA_UPLOAD_TYPE.equals(type)); + boolean isUpload = (typeIndex >= 0 && + (PICASA_UPLOAD_TYPE.equals(type) || PICASA_UPLOADAUTO_TYPE.equals(type))); String account = accounts.get(user); if (account == null) { @@ -367,8 +413,6 @@ public class PicasaSource extends PhotoSource { if (data.thumbnailUrl == null || data.updated == updated) { data.thumbnailUrl = thumbnailUrl; } - - cursor.moveToNext(); } } cursor.close(); @@ -403,9 +447,6 @@ public class PicasaSource extends PhotoSource { } catch (FileNotFoundException fnf) { log(TAG, "file not found: " + fnf); is = null; - } catch (IOException ioe) { - log(TAG, "i/o exception: " + ioe); - is = null; } if (is != null) { diff --git a/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java b/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java index 6dc3ddf..42f2eb0 100644 --- a/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java +++ b/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java @@ -21,10 +21,9 @@ import android.database.DataSetObserver; import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.TextView; import android.widget.ListAdapter; +import android.widget.TextView; import java.util.Arrays; import java.util.List; @@ -39,13 +38,11 @@ public class SectionedAlbumDataAdapter extends DataSetObserver implements ListAd private final LayoutInflater mInflater; private final int mLayout; private final AlbumDataAdapter mAlbumData; - private final Context mContext; private int[] sections; public SectionedAlbumDataAdapter(Context context, SharedPreferences settings, int headerLayout, int itemLayout, List<PhotoSource.AlbumData> objects) { mLayout = headerLayout; - mContext = context; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mAlbumData = new AlbumDataAdapter(context, settings, itemLayout, objects); mAlbumData.sort(new AlbumDataAdapter.AccountComparator()); @@ -53,6 +50,16 @@ public class SectionedAlbumDataAdapter extends DataSetObserver implements ListAd mAlbumData.registerDataSetObserver(this); } + boolean areAllSelected() { + return mAlbumData != null && mAlbumData.areAllSelected(); + } + + void selectAll(boolean select) { + if (mAlbumData != null) { + mAlbumData.selectAll(select); + } + } + // DataSetObserver @Override diff --git a/src/com/android/dreams/phototable/SoftLandingInterpolator.java b/src/com/android/dreams/phototable/SoftLandingInterpolator.java index bb2c1bd..6a6020d 100644 --- a/src/com/android/dreams/phototable/SoftLandingInterpolator.java +++ b/src/com/android/dreams/phototable/SoftLandingInterpolator.java @@ -16,10 +16,9 @@ package com.android.dreams.phototable; -import android.view.animation.Interpolator; import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; -import android.util.Log; /** * An interpolator where the rate of change starts out quickly and @@ -31,7 +30,6 @@ public class SoftLandingInterpolator implements Interpolator { private final DecelerateInterpolator slide; private final float mI; private final float mO; - private final float lowerRange; private final float upperRange; private final float bottom; private final float top; @@ -44,7 +42,6 @@ public class SoftLandingInterpolator implements Interpolator { final float epsilon = Math.min(mI / 2f, (1f - mI) / 2f); bottom = mI - epsilon; top = mI + epsilon; - lowerRange = top; upperRange = 1f - bottom; } diff --git a/src/com/android/dreams/phototable/StockSource.java b/src/com/android/dreams/phototable/StockSource.java index 3d44309..be2a860 100644 --- a/src/com/android/dreams/phototable/StockSource.java +++ b/src/com/android/dreams/phototable/StockSource.java @@ -17,45 +17,55 @@ package com.android.dreams.phototable; import android.content.Context; import android.content.SharedPreferences; -import android.util.Log; import java.io.InputStream; -import java.util.Collection; import java.util.ArrayList; +import java.util.Collection; /** * Picks a random image from the local store. */ -public class - -StockSource extends PhotoSource { +public class StockSource extends PhotoSource { public static final String ALBUM_ID = "com.android.dreams.phototable.StockSource"; private static final String TAG = "PhotoTable.StockSource"; private static final int[] PHOTOS = { R.drawable.blank_photo }; + private final ArrayList<ImageData> mImageCache; + private final ArrayList<AlbumData> mAlbumCache; + private final ArrayList<ImageData> mImageList; private final ArrayList<AlbumData> mAlbumList; private final String mStockPhotoName; - private int mNextPosition; public StockSource(Context context, SharedPreferences settings) { super(context, settings, null); mSourceName = TAG; mStockPhotoName = mResources.getString(R.string.stock_photo_album_name, "Default Photos"); + mImageCache = new ArrayList<ImageData>(PHOTOS.length); + mAlbumCache = new ArrayList<AlbumData>(1); mImageList = new ArrayList<ImageData>(PHOTOS.length); mAlbumList = new ArrayList<AlbumData>(1); + + AlbumData albumData = new AlbumData(); + albumData.id = ALBUM_ID; + albumData.account = mStockPhotoName; + albumData.title = mStockPhotoName; + mAlbumCache.add(albumData); + + for (int i = 0; i < PHOTOS.length; i++) { + ImageData imageData = new ImageData(); + imageData.id = Integer.toString(i); + mImageCache.add(imageData); + } + fillQueue(); } @Override public Collection<AlbumData> findAlbums() { if (mAlbumList.isEmpty()) { - AlbumData data = new AlbumData(); - data.id = ALBUM_ID; - data.account = mStockPhotoName; - data.title = mStockPhotoName; - mAlbumList.add(data); + mAlbumList.addAll(mAlbumCache); } log(TAG, "returning a list of albums: " + mAlbumList.size()); return mAlbumList; @@ -64,11 +74,7 @@ StockSource extends PhotoSource { @Override protected Collection<ImageData> findImages(int howMany) { if (mImageList.isEmpty()) { - for (int i = 0; i < PHOTOS.length; i++) { - ImageData data = new ImageData(); - data.id = Integer.toString(PHOTOS[i]); - mImageList.add(data); - } + mImageList.addAll(mImageCache); } return mImageList; } @@ -78,7 +84,8 @@ StockSource extends PhotoSource { InputStream is = null; try { log(TAG, "opening:" + data.id); - is = mResources.openRawResource(Integer.valueOf(data.id)); + int idx = Integer.valueOf(data.id); + is = mResources.openRawResource(PHOTOS[idx]); } catch (Exception ex) { log(TAG, ex.toString()); is = null; @@ -86,5 +93,23 @@ StockSource extends PhotoSource { return is; } + + @Override + public ImageData naturalNext(ImageData current) { + int idx = Integer.valueOf(current.id); + idx = (idx + 1) % PHOTOS.length; + return mImageCache.get(idx); + } + + @Override + public ImageData naturalPrevious(ImageData current) { + int idx = Integer.valueOf(current.id); + idx = (PHOTOS.length + idx - 1) % PHOTOS.length; + return mImageCache.get(idx); + } + + @Override + protected void donePaging(ImageData current) { + } } |