summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRicardo Cerqueira <cyanogenmod@cerqueira.org>2013-07-25 00:47:41 +0100
committerRicardo Cerqueira <cyanogenmod@cerqueira.org>2013-07-25 00:47:41 +0100
commit412c2e04636a24e7da24bb26b751b9482a83d68f (patch)
tree14c693521ef1408797d22f722d066b41a936367d
parent1a180510e6421467db8cafa641130fb1dbb6aee7 (diff)
parentdd5f6f03990d50bbb353b2c4d77a90f8211b5a74 (diff)
downloadandroid_packages_screensavers_PhotoTable-stable/cm-10.2.tar.gz
android_packages_screensavers_PhotoTable-stable/cm-10.2.tar.bz2
android_packages_screensavers_PhotoTable-stable/cm-10.2.zip
Merge tag 'android-4.3_r2.1' into cm-10.2cm-10.2.1cm-10.2.0cm-10.2-M1stable/cm-10.2cm-10.2
Android 4.3 release 2.1
-rw-r--r--AndroidManifest.xml1
-rw-r--r--res/layout-sw800dp/table.xml17
-rw-r--r--res/layout/album.xml2
-rw-r--r--res/layout/settingslist.xml31
-rw-r--r--res/layout/table.xml26
-rw-r--r--res/menu/photodream_settings_menu.xml21
-rw-r--r--res/values-af/strings.xml3
-rw-r--r--res/values-am/strings.xml3
-rw-r--r--res/values-ar/strings.xml3
-rw-r--r--res/values-be/strings.xml3
-rw-r--r--res/values-bg/strings.xml3
-rw-r--r--res/values-ca/strings.xml3
-rw-r--r--res/values-cs/strings.xml3
-rw-r--r--res/values-da/strings.xml3
-rw-r--r--res/values-de/strings.xml3
-rw-r--r--res/values-el/strings.xml3
-rw-r--r--res/values-en-rGB/strings.xml3
-rw-r--r--res/values-es-rUS/strings.xml3
-rw-r--r--res/values-es/strings.xml3
-rw-r--r--res/values-et/strings.xml3
-rw-r--r--res/values-fa/strings.xml3
-rw-r--r--res/values-fi/strings.xml3
-rw-r--r--res/values-fr/strings.xml3
-rw-r--r--res/values-hi/strings.xml3
-rw-r--r--res/values-hr/strings.xml3
-rw-r--r--res/values-hu/strings.xml3
-rw-r--r--res/values-in/strings.xml3
-rw-r--r--res/values-it/strings.xml3
-rw-r--r--res/values-iw/strings.xml3
-rw-r--r--res/values-ja/strings.xml3
-rw-r--r--res/values-ko/strings.xml3
-rw-r--r--res/values-land-notouch/config.xml30
-rw-r--r--res/values-lt/strings.xml3
-rw-r--r--res/values-lv/strings.xml3
-rw-r--r--res/values-ms/strings.xml3
-rw-r--r--res/values-nb/strings.xml5
-rw-r--r--res/values-nl/strings.xml3
-rw-r--r--res/values-pl/strings.xml5
-rw-r--r--res/values-pt-rPT/strings.xml3
-rw-r--r--res/values-pt/strings.xml3
-rw-r--r--res/values-ro/strings.xml3
-rw-r--r--res/values-ru/strings.xml3
-rw-r--r--res/values-sk/strings.xml3
-rw-r--r--res/values-sl/strings.xml3
-rw-r--r--res/values-sr/strings.xml3
-rw-r--r--res/values-sv/strings.xml3
-rw-r--r--res/values-sw/strings.xml5
-rw-r--r--res/values-sw600dp/config.xml3
-rw-r--r--res/values-sw800dp/config.xml9
-rw-r--r--res/values-th/strings.xml3
-rw-r--r--res/values-tl/strings.xml3
-rw-r--r--res/values-tr/strings.xml3
-rw-r--r--res/values-uk/strings.xml3
-rw-r--r--res/values-vi/strings.xml3
-rw-r--r--res/values-zh-rCN/strings.xml5
-rw-r--r--res/values-zh-rTW/strings.xml3
-rw-r--r--res/values-zu/strings.xml3
-rw-r--r--res/values/colors.xml1
-rw-r--r--res/values/config.xml28
-rw-r--r--res/values/strings.xml9
-rw-r--r--src/com/android/dreams/phototable/AlbumDataAdapter.java30
-rw-r--r--src/com/android/dreams/phototable/AlbumSettings.java22
-rw-r--r--src/com/android/dreams/phototable/CursorPhotoSource.java85
-rw-r--r--src/com/android/dreams/phototable/DragGestureDetector.java113
-rw-r--r--src/com/android/dreams/phototable/EdgeSwipeDetector.java76
-rw-r--r--src/com/android/dreams/phototable/FlipperDream.java1
-rw-r--r--src/com/android/dreams/phototable/FlipperDreamSettings.java92
-rw-r--r--src/com/android/dreams/phototable/KeyboardInterpreter.java122
-rw-r--r--src/com/android/dreams/phototable/LocalSource.java96
-rw-r--r--src/com/android/dreams/phototable/PhotoCarousel.java24
-rw-r--r--src/com/android/dreams/phototable/PhotoSource.java78
-rw-r--r--src/com/android/dreams/phototable/PhotoSourcePlexor.java20
-rw-r--r--src/com/android/dreams/phototable/PhotoTable.java782
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDream.java6
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDreamSettings.java34
-rw-r--r--src/com/android/dreams/phototable/PhotoTouchListener.java61
-rw-r--r--src/com/android/dreams/phototable/PicasaSource.java123
-rw-r--r--src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java15
-rw-r--r--src/com/android/dreams/phototable/SoftLandingInterpolator.java5
-rw-r--r--src/com/android/dreams/phototable/StockSource.java59
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) {
+ }
}