<?xml version="1.0" encoding="utf-8"?>
-<manifest android:versionCode="40003"
- android:versionName="1.1.40003"
+<manifest android:versionCode="40004"
+ android:versionName="1.1.40004"
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.gallery3d">
</intent-filter>
</activity>
- <activity android:name="com.android.photos.GalleryActivity"
+ <activity
+ android:name="com.android.photos.GalleryActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Theme.Photos.Gallery"
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter>
+ <!-- We do NOT support the PICK intent, we add these intent-filter for
+ backward compatibility. Handle it as GET_CONTENT. -->
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ <data android:mimeType="video/*" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PICK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/image" />
+ <data android:mimeType="vnd.android.cursor.dir/video" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="com.android.gallery3d.app.Gallery"
+ android:label="@string/app_name"
+ android:configChanges="keyboardHidden|orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/3gpp2" />
<data android:mimeType="application/sdp" />
</intent-filter>
- <!-- We do NOT support the PICK intent, we add these intent-filter for
- backward compatibility. Handle it as GET_CONTENT. -->
- <intent-filter>
- <action android:name="android.intent.action.PICK" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="image/*" />
- <data android:mimeType="video/*" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.PICK" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.dir/image" />
- <data android:mimeType="vnd.android.cursor.dir/video" />
- </intent-filter>
</activity>
<activity android:name="com.android.photos.FullscreenViewer"
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.camera.ui.RotatableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/camera_controls"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <com.android.camera.ui.RotatableLayout
- style="@style/CameraControls"
- android:id="@+id/menu_button"
- android:layout_gravity="center" >
+ style="@style/CameraControls"
+ android:layout_gravity="center" >
<View
android:id="@+id/blocker"
android:layout_height="match_parent"
android:layout_width="@dimen/switcher_size"
- android:background="@drawable/switcher_bg"
android:clickable="true"
android:layout_gravity="right" />
android:contentDescription="@string/accessibility_menu_button"
android:layout_gravity="right|top"
android:layout_marginRight="2dip" />
- </com.android.camera.ui.RotatableLayout>
- <com.android.camera.ui.RotatableLayout
- style="@style/CameraControls"
- android:id="@+id/switcher_control"
- android:layout_gravity="right|center_horizontal" >
<com.android.camera.ui.CameraSwitcher
android:id="@+id/camera_switcher"
style="@style/SwitcherButton"
android:layout_gravity="right|bottom"
android:layout_marginRight="2dip"
android:contentDescription="@string/accessibility_mode_picker" />
- </com.android.camera.ui.RotatableLayout>
-
- <com.android.camera.ShutterButton
- android:id="@+id/shutter_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="right|center_vertical"
- android:layout_marginRight="@dimen/shutter_offset"
- android:clickable="true"
- android:contentDescription="@string/accessibility_shutter_button"
- android:focusable="true"
- android:scaleType="center"
- android:src="@drawable/btn_new_shutter" />
-</FrameLayout>
\ No newline at end of file
+ <com.android.camera.ShutterButton
+ android:id="@+id/shutter_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|center_vertical"
+ android:layout_marginRight="@dimen/shutter_offset"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_shutter_button"
+ android:focusable="true"
+ android:scaleType="center"
+ android:src="@drawable/btn_new_shutter" />
+</com.android.camera.ui.RotatableLayout>
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.camera.ui.RotatableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/camera_controls"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <com.android.camera.ui.RotatableLayout
- style="@style/CameraControls"
- android:id="@+id/menu_button"
- android:layout_gravity="center" >
+ style="@style/CameraControls"
+ android:layout_gravity="center" >
<View
android:id="@+id/blocker"
android:layout_width="match_parent"
android:layout_height="@dimen/switcher_size"
android:layout_gravity="bottom"
- android:background="@drawable/switcher_bg"
android:clickable="true" />
<include layout="@layout/menu_indicators"
android:layout_marginBottom="2dip"
android:contentDescription="@string/accessibility_menu_button" />
- </com.android.camera.ui.RotatableLayout>
-
- <com.android.camera.ui.RotatableLayout
- style="@style/CameraControls"
- android:id="@+id/switcher_control"
- android:layout_gravity="bottom|center_horizontal">
-
- <com.android.camera.ui.CameraSwitcher
+ <com.android.camera.ui.CameraSwitcher
android:id="@+id/camera_switcher"
style="@style/SwitcherButton"
android:layout_gravity="bottom|left"
android:layout_marginBottom="2dip"
android:contentDescription="@string/accessibility_mode_picker" />
- </com.android.camera.ui.RotatableLayout>
-
- <com.android.camera.ShutterButton
- android:id="@+id/shutter_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center_horizontal"
- android:layout_marginBottom="@dimen/shutter_offset"
- android:clickable="true"
- android:contentDescription="@string/accessibility_shutter_button"
- android:focusable="true"
- android:scaleType="center"
- android:src="@drawable/btn_new_shutter" />
-</FrameLayout>
\ No newline at end of file
+ <com.android.camera.ShutterButton
+ android:id="@+id/shutter_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/shutter_offset"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_shutter_button"
+ android:focusable="true"
+ android:scaleType="center"
+ android:src="@drawable/btn_new_shutter" />
+
+</com.android.camera.ui.RotatableLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <GridView
+ android:id="@id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:numColumns="auto_fit"
+ android:columnWidth="@dimen/album_set_item_width"
+ android:stretchMode="columnWidth"
+ android:drawSelectorOnTop="true"
+ android:padding="10dp"
+ android:horizontalSpacing="10dp"
+ android:verticalSpacing="10dp" />
+
+ <TextView
+ android:id="@id/android:empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/empty_album" />
+
+</FrameLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="#FFF" >
+
+ <TextView
+ android:id="@+id/album_set_item_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="10dp"
+ android:layout_marginTop="10dp"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/album_set_item_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@+id/album_set_item_title"
+ android:layout_below="@+id/album_set_item_title"
+ android:layout_marginBottom="10dp"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <ImageView
+ android:id="@+id/album_set_item_image"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/album_set_item_image_height"
+ android:layout_below="@+id/album_set_item_date"
+ android:scaleType="centerCrop" />
+
+ <ProgressBar
+ android:id="@+id/album_set_item_upload_progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ android:layout_alignParentBottom="true" />
+</RelativeLayout>
\ No newline at end of file
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <include layout="@layout/camera_controls" />
+ <include layout="@layout/camera_controls"
+ style="@style/CameraControls"
+ android:layout_centerInParent="true" />
</RelativeLayout>
\ No newline at end of file
android:paddingLeft="8dp"
android:paddingRight="8dp" >
- <com.android.photos.views.GalleryThumbnailView
+ <GridView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
- android:drawSelectorOnTop="true" />
+ android:drawSelectorOnTop="true"
+ android:numColumns="auto_fit"
+ android:stretchMode="columnWidth"
+ android:columnWidth="200dip"
+ android:horizontalSpacing="4dip"
+ android:verticalSpacing="4dip"
+ android:padding="4dip" />
<TextView
android:id="@id/android:empty"
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="200dip"
+ android:id="@+id/thumbnail">
+
+</ImageView>
\ No newline at end of file
<string name="reset" msgid="9013181350779592937">"Stel terug"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Huidige prenttoestand"</string>
+ <string name="imageState" msgid="8632586742752891968">"Toegepaste uitwerkings"</string>
<string name="compare_original" msgid="8140838959007796977">"Vergelyk"</string>
<string name="apply_effect" msgid="1218288221200568947">"Pas toe"</string>
<string name="reset_effect" msgid="7712605581024929564">"Stel terug"</string>
<string name="reset" msgid="9013181350779592937">"ዳግም አስጀምር"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"የአሁኑ ምስል ሁኔታ"</string>
+ <string name="imageState" msgid="8632586742752891968">"የተተገበሩ ተጽዕኖዎች"</string>
<string name="compare_original" msgid="8140838959007796977">"አወዳድር"</string>
<string name="apply_effect" msgid="1218288221200568947">"ተግብር"</string>
<string name="reset_effect" msgid="7712605581024929564">"ዳግም አስጀምር"</string>
<string name="reset" msgid="9013181350779592937">"إعادة تعيين"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"حالة الصورة الحالية"</string>
+ <string name="imageState" msgid="8632586742752891968">"التأثيرات المطبقة"</string>
<string name="compare_original" msgid="8140838959007796977">"مقارنة"</string>
<string name="apply_effect" msgid="1218288221200568947">"تطبيق"</string>
<string name="reset_effect" msgid="7712605581024929564">"إعادة تعيين"</string>
<string name="reset" msgid="9013181350779592937">"Скінуць"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Бягучы стан малюнка"</string>
+ <string name="imageState" msgid="8632586742752891968">"Прымененыя эфекты"</string>
<string name="compare_original" msgid="8140838959007796977">"Параўнаць"</string>
<string name="apply_effect" msgid="1218288221200568947">"Паспрабаваць"</string>
<string name="reset_effect" msgid="7712605581024929564">"Скінуць"</string>
<string name="reset" msgid="9013181350779592937">"Повторно задаване"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Текущо състояние на изображението"</string>
+ <string name="imageState" msgid="8632586742752891968">"Приложени ефекти"</string>
<string name="compare_original" msgid="8140838959007796977">"Сравняване"</string>
<string name="apply_effect" msgid="1218288221200568947">"Прилагане"</string>
<string name="reset_effect" msgid="7712605581024929564">"Нулиране"</string>
<string name="reset" msgid="9013181350779592937">"Restableix"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estat de la imatge actual"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efectes aplicats"</string>
<string name="compare_original" msgid="8140838959007796977">"Compara"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplica"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restableix"</string>
<string name="reset" msgid="9013181350779592937">"Obnovit"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuální stav obrázku"</string>
+ <string name="imageState" msgid="8632586742752891968">"Použité efekty"</string>
<string name="compare_original" msgid="8140838959007796977">"Porovnat"</string>
<string name="apply_effect" msgid="1218288221200568947">"Použít"</string>
<string name="reset_effect" msgid="7712605581024929564">"Obnovit"</string>
<string name="reset" msgid="9013181350779592937">"Nulstil"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Nuværende billedtilstand"</string>
+ <string name="imageState" msgid="8632586742752891968">"Anvendte effekter"</string>
<string name="compare_original" msgid="8140838959007796977">"Sammenlign"</string>
<string name="apply_effect" msgid="1218288221200568947">"Anvend"</string>
<string name="reset_effect" msgid="7712605581024929564">"Nulstil"</string>
<string name="reset" msgid="9013181350779592937">"Zurücksetzen"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktueller Bildstatus"</string>
+ <string name="imageState" msgid="8632586742752891968">"Angewendete Effekte"</string>
<string name="compare_original" msgid="8140838959007796977">"Vergleichen"</string>
<string name="apply_effect" msgid="1218288221200568947">"Übernehmen"</string>
<string name="reset_effect" msgid="7712605581024929564">"Zurücksetzen"</string>
<string name="reset" msgid="9013181350779592937">"Επαναφορά"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Κατάσταση τρέχουσας εικόνας"</string>
+ <string name="imageState" msgid="8632586742752891968">"Εφαρμοσμένα εφέ"</string>
<string name="compare_original" msgid="8140838959007796977">"Σύγκριση"</string>
<string name="apply_effect" msgid="1218288221200568947">"Εφαρμογή"</string>
<string name="reset_effect" msgid="7712605581024929564">"Επαναφορά"</string>
<string name="reset" msgid="9013181350779592937">"Reset"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Current Image State"</string>
+ <string name="imageState" msgid="8632586742752891968">"Applied Effects"</string>
<string name="compare_original" msgid="8140838959007796977">"Compare"</string>
<string name="apply_effect" msgid="1218288221200568947">"Apply"</string>
<string name="reset_effect" msgid="7712605581024929564">"Reset"</string>
<string name="reset" msgid="9013181350779592937">"Restablecer"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado actual de la imagen"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efectos aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restablecer"</string>
<string name="reset" msgid="9013181350779592937">"Restablecer"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado de imagen actual"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efectos aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restablecer"</string>
<string name="reset" msgid="9013181350779592937">"Lähtesta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Pildi praegune olek"</string>
+ <string name="imageState" msgid="8632586742752891968">"Rakendatud efektid"</string>
<string name="compare_original" msgid="8140838959007796977">"Võrdle"</string>
<string name="apply_effect" msgid="1218288221200568947">"Rakenda"</string>
<string name="reset_effect" msgid="7712605581024929564">"Lähtesta"</string>
<string name="reset" msgid="9013181350779592937">"بازنشانی"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"وضعیت کنونی تصویر"</string>
+ <string name="imageState" msgid="8632586742752891968">"جلوههای اعمال شده"</string>
<string name="compare_original" msgid="8140838959007796977">"مقایسه"</string>
<string name="apply_effect" msgid="1218288221200568947">"اعمال کردن"</string>
<string name="reset_effect" msgid="7712605581024929564">"بازنشانی"</string>
<string name="reset" msgid="9013181350779592937">"Palauta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Kuvan nykyinen tila"</string>
+ <string name="imageState" msgid="8632586742752891968">"Käytetyt tehosteet"</string>
<string name="compare_original" msgid="8140838959007796977">"Vertaa"</string>
<string name="apply_effect" msgid="1218288221200568947">"Käytä"</string>
<string name="reset_effect" msgid="7712605581024929564">"Palauta"</string>
<string name="reset" msgid="9013181350779592937">"Réinitialiser"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"État actuel de l\'image"</string>
+ <string name="imageState" msgid="8632586742752891968">"Effets appliqués"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparer"</string>
<string name="apply_effect" msgid="1218288221200568947">"Appliquer"</string>
<string name="reset_effect" msgid="7712605581024929564">"Réinitialiser"</string>
<string name="reset" msgid="9013181350779592937">"रीसेट करें"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"वर्तमान चित्र स्थिति"</string>
+ <string name="imageState" msgid="8632586742752891968">"लागू किए गए प्रभाव"</string>
<string name="compare_original" msgid="8140838959007796977">"तुलना करें"</string>
<string name="apply_effect" msgid="1218288221200568947">"लागू करें"</string>
<string name="reset_effect" msgid="7712605581024929564">"रीसेट करें"</string>
<string name="reset" msgid="9013181350779592937">"Poništi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Trenutačno stanje slike"</string>
+ <string name="imageState" msgid="8632586742752891968">"Primijenjeni efekti"</string>
<string name="compare_original" msgid="8140838959007796977">"Usporedi"</string>
<string name="apply_effect" msgid="1218288221200568947">"Primijeni"</string>
<string name="reset_effect" msgid="7712605581024929564">"Poništi"</string>
<string name="reset" msgid="9013181350779592937">"Visszaállítás"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuális fényképállapot"</string>
+ <string name="imageState" msgid="8632586742752891968">"Alkalmazott hatások"</string>
<string name="compare_original" msgid="8140838959007796977">"Összehasonlítás"</string>
<string name="apply_effect" msgid="1218288221200568947">"Alkalmaz"</string>
<string name="reset_effect" msgid="7712605581024929564">"Visszaállítás"</string>
<string name="reset" msgid="9013181350779592937">"Setel Ulang"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Status Gambar Saat Ini"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efek yang Diterapkan"</string>
<string name="compare_original" msgid="8140838959007796977">"Bandingkan"</string>
<string name="apply_effect" msgid="1218288221200568947">"Terapkan"</string>
<string name="reset_effect" msgid="7712605581024929564">"Setel Ulang"</string>
<string name="reset" msgid="9013181350779592937">"Reimposta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Stato immagine attuale"</string>
+ <string name="imageState" msgid="8632586742752891968">"Effetti applicati"</string>
<string name="compare_original" msgid="8140838959007796977">"Confronta"</string>
<string name="apply_effect" msgid="1218288221200568947">"Applica"</string>
<string name="reset_effect" msgid="7712605581024929564">"Reimposta"</string>
<string name="reset" msgid="9013181350779592937">"אפס"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"מצב תמונה נוכחית"</string>
+ <string name="imageState" msgid="8632586742752891968">"אפקטים שהוחלו"</string>
<string name="compare_original" msgid="8140838959007796977">"השווה"</string>
<string name="apply_effect" msgid="1218288221200568947">"החל"</string>
<string name="reset_effect" msgid="7712605581024929564">"אפס"</string>
<string name="reset" msgid="9013181350779592937">"リセット"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"現在の画像ステータス"</string>
+ <string name="imageState" msgid="8632586742752891968">"適用済みの効果"</string>
<string name="compare_original" msgid="8140838959007796977">"比較"</string>
<string name="apply_effect" msgid="1218288221200568947">"適用"</string>
<string name="reset_effect" msgid="7712605581024929564">"リセット"</string>
<string name="reset" msgid="9013181350779592937">"초기화"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"현재 이미지 상태"</string>
+ <string name="imageState" msgid="8632586742752891968">"적용된 효과"</string>
<string name="compare_original" msgid="8140838959007796977">"비교하기"</string>
<string name="apply_effect" msgid="1218288221200568947">"적용"</string>
<string name="reset_effect" msgid="7712605581024929564">"초기화"</string>
<string name="reset" msgid="9013181350779592937">"Nust. iš naujo"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Dabartinė vaizdo būsena"</string>
+ <string name="imageState" msgid="8632586742752891968">"Pritaikyti efektai"</string>
<string name="compare_original" msgid="8140838959007796977">"Palyginti"</string>
<string name="apply_effect" msgid="1218288221200568947">"Taikyti"</string>
<string name="reset_effect" msgid="7712605581024929564">"Nust. iš naujo"</string>
<string name="reset" msgid="9013181350779592937">"Atiestatīt"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Pašreizējais attēla statuss"</string>
+ <string name="imageState" msgid="8632586742752891968">"Izmantotie efekti"</string>
<string name="compare_original" msgid="8140838959007796977">"Salīdzināt"</string>
<string name="apply_effect" msgid="1218288221200568947">"Lietot"</string>
<string name="reset_effect" msgid="7712605581024929564">"Atiestatīt"</string>
<string name="reset" msgid="9013181350779592937">"Tetapkan semula"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Keadaan Imej Semasa"</string>
+ <string name="imageState" msgid="8632586742752891968">"Kesan Digunakan"</string>
<string name="compare_original" msgid="8140838959007796977">"Bandingkan"</string>
<string name="apply_effect" msgid="1218288221200568947">"Gunakan"</string>
<string name="reset_effect" msgid="7712605581024929564">"Tetapkan semula"</string>
<string name="reset" msgid="9013181350779592937">"Tilbakestill"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Nåværende bildetilstand"</string>
+ <string name="imageState" msgid="8632586742752891968">"Brukte effekter"</string>
<string name="compare_original" msgid="8140838959007796977">"Sammenlign"</string>
<string name="apply_effect" msgid="1218288221200568947">"Bruk"</string>
<string name="reset_effect" msgid="7712605581024929564">"Tilbakestill"</string>
<string name="reset" msgid="9013181350779592937">"Opnieuw instellen"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Huidige afbeeldingsstatus"</string>
+ <string name="imageState" msgid="8632586742752891968">"Toegepaste effecten"</string>
<string name="compare_original" msgid="8140838959007796977">"Vergelijken"</string>
<string name="apply_effect" msgid="1218288221200568947">"Toepassen"</string>
<string name="reset_effect" msgid="7712605581024929564">"Opnieuw instellen"</string>
<string name="reset" msgid="9013181350779592937">"Resetuj"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Obecny stan obrazu"</string>
+ <string name="imageState" msgid="8632586742752891968">"Zastosowane efekty"</string>
<string name="compare_original" msgid="8140838959007796977">"Porównaj"</string>
<string name="apply_effect" msgid="1218288221200568947">"Zastosuj"</string>
<string name="reset_effect" msgid="7712605581024929564">"Resetuj"</string>
<string name="reset" msgid="9013181350779592937">"Repor"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado da Imagem Atual"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efeitos Aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Repor"</string>
<string name="reset" msgid="9013181350779592937">"Restaurar"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado atual da imagem"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efeitos aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restaurar"</string>
<string name="reset" msgid="9013181350779592937">"Resetaţi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Starea curentă a imaginii"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efecte aplicate"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparaţi"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicaţi"</string>
<string name="reset_effect" msgid="7712605581024929564">"Resetaţi"</string>
<string name="reset" msgid="9013181350779592937">"Сброс"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Состояние изображения"</string>
+ <string name="imageState" msgid="8632586742752891968">"Эффекты"</string>
<string name="compare_original" msgid="8140838959007796977">"Сравнить"</string>
<string name="apply_effect" msgid="1218288221200568947">"Применить:"</string>
<string name="reset_effect" msgid="7712605581024929564">"Сброс"</string>
<string name="reset" msgid="9013181350779592937">"Obnoviť"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuálny stav obrázka"</string>
+ <string name="imageState" msgid="8632586742752891968">"Použité efekty"</string>
<string name="compare_original" msgid="8140838959007796977">"Porovnať"</string>
<string name="apply_effect" msgid="1218288221200568947">"Použiť"</string>
<string name="reset_effect" msgid="7712605581024929564">"Obnoviť"</string>
<string name="reset" msgid="9013181350779592937">"Ponastavi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Trenutno stanje slike"</string>
+ <string name="imageState" msgid="8632586742752891968">"Uporabljeni učinki"</string>
<string name="compare_original" msgid="8140838959007796977">"Primerjaj"</string>
<string name="apply_effect" msgid="1218288221200568947">"Uporabi"</string>
<string name="reset_effect" msgid="7712605581024929564">"Ponastavi"</string>
<string name="reset" msgid="9013181350779592937">"Поново постави"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Актуелни статус слике"</string>
+ <string name="imageState" msgid="8632586742752891968">"Примењени ефекти"</string>
<string name="compare_original" msgid="8140838959007796977">"Упореди"</string>
<string name="apply_effect" msgid="1218288221200568947">"Примени"</string>
<string name="reset_effect" msgid="7712605581024929564">"Поново постави"</string>
<string name="reset" msgid="9013181350779592937">"Återställ"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuellt bildläge"</string>
+ <string name="imageState" msgid="8632586742752891968">"Effekter som används"</string>
<string name="compare_original" msgid="8140838959007796977">"Jämför"</string>
<string name="apply_effect" msgid="1218288221200568947">"Använd"</string>
<string name="reset_effect" msgid="7712605581024929564">"Återställ"</string>
<string name="reset" msgid="9013181350779592937">"Weka upya"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Hali ya Sasa ya Picha"</string>
+ <string name="imageState" msgid="8632586742752891968">"Madoido Yanayotumiwa"</string>
<string name="compare_original" msgid="8140838959007796977">"Linganisha"</string>
<string name="apply_effect" msgid="1218288221200568947">"Tekeleza"</string>
<string name="reset_effect" msgid="7712605581024929564">"Weka upya"</string>
<string name="reset" msgid="9013181350779592937">"รีเซ็ต"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"สถานะภาพปัจจุบัน"</string>
+ <string name="imageState" msgid="8632586742752891968">"เอฟเ็ฟ็กต์ที่ใช้"</string>
<string name="compare_original" msgid="8140838959007796977">"เปรียบเทียบ"</string>
<string name="apply_effect" msgid="1218288221200568947">"ใช้"</string>
<string name="reset_effect" msgid="7712605581024929564">"รีเซ็ต"</string>
<string name="reset" msgid="9013181350779592937">"I-reset"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Kasalukuyang Katayuan ng Larawan"</string>
+ <string name="imageState" msgid="8632586742752891968">"Mga Nakalapat na Effect"</string>
<string name="compare_original" msgid="8140838959007796977">"Ihambing"</string>
<string name="apply_effect" msgid="1218288221200568947">"Ilapat"</string>
<string name="reset_effect" msgid="7712605581024929564">"I-reset"</string>
<string name="reset" msgid="9013181350779592937">"Sıfırla"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Mevcut Resim Durumu"</string>
+ <string name="imageState" msgid="8632586742752891968">"Uygulanan Efektler"</string>
<string name="compare_original" msgid="8140838959007796977">"Karşılaştır"</string>
<string name="apply_effect" msgid="1218288221200568947">"Uygula"</string>
<string name="reset_effect" msgid="7712605581024929564">"Sıfırla"</string>
<string name="reset" msgid="9013181350779592937">"Скинути"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Поточний стан зображення"</string>
+ <string name="imageState" msgid="8632586742752891968">"Застосовані ефекти"</string>
<string name="compare_original" msgid="8140838959007796977">"Порівняти"</string>
<string name="apply_effect" msgid="1218288221200568947">"Застосувати"</string>
<string name="reset_effect" msgid="7712605581024929564">"Скинути"</string>
<string name="reset" msgid="9013181350779592937">"Đặt lại"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Trạng thái hình ảnh hiện tại"</string>
+ <string name="imageState" msgid="8632586742752891968">"Các hiệu ứng được áp dụng"</string>
<string name="compare_original" msgid="8140838959007796977">"So sánh"</string>
<string name="apply_effect" msgid="1218288221200568947">"Áp dụng"</string>
<string name="reset_effect" msgid="7712605581024929564">"Đặt lại"</string>
<string name="reset" msgid="9013181350779592937">"重置"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"当前的图片状态"</string>
+ <string name="imageState" msgid="8632586742752891968">"运用的效果"</string>
<string name="compare_original" msgid="8140838959007796977">"比较"</string>
<string name="apply_effect" msgid="1218288221200568947">"应用"</string>
<string name="reset_effect" msgid="7712605581024929564">"重置"</string>
<string name="reset" msgid="9013181350779592937">"重設"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"目前圖片狀態"</string>
+ <string name="imageState" msgid="8632586742752891968">"套用的效果"</string>
<string name="compare_original" msgid="8140838959007796977">"比較"</string>
<string name="apply_effect" msgid="1218288221200568947">"套用"</string>
<string name="reset_effect" msgid="7712605581024929564">"重設"</string>
<string name="reset" msgid="9013181350779592937">"Setha kabusha"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Isimo sesithombe samanje"</string>
+ <string name="imageState" msgid="8632586742752891968">"Imiphumela esetshenzisiwe"</string>
<string name="compare_original" msgid="8140838959007796977">"Qhathanisa"</string>
<string name="apply_effect" msgid="1218288221200568947">"Sebenzisa"</string>
<string name="reset_effect" msgid="7712605581024929564">"Setha kabusha"</string>
<dimen name="face_circle_stroke">2dip</dimen>
<dimen name="zoom_font_size">14pt</dimen>
<dimen name="shutter_offset">-22dp</dimen>
- <dimen name="margin_systemui_offset">6dip</dimen>
<dimen name="size_thumbnail">200dip</dimen>
<dimen name="size_preview">600dip</dimen>
+ <dimen name="navigation_bar_height">48dip</dimen>
+ <dimen name="navigation_bar_width">42dip</dimen>
</resources>
<dimen name="stack_photo_width">160dp</dimen>
<dimen name="stack_photo_height">120dp</dimen>
- <!-- configuration for album set page -->
+ <!-- configuration for legacy album set page -->
<integer name="albumset_rows_land">2</integer>
<integer name="albumset_rows_port">3</integer>
<dimen name="albumset_padding_top">7dp</dimen>
<!-- configuration for filtershow UI -->
<dimen name="thumbnail_size">96dip</dimen>
<dimen name="thumbnail_margin">3dip</dimen>
+
+ <!-- configuration for album set page -->
+ <dimen name="album_set_item_image_height">100dp</dimen>
+ <dimen name="album_set_item_width">160dp</dimen>
</resources>
private View mCameraControls;
private View mControlsBackground;
private View mPieMenuButton;
- private View mSwitcherControl;
private Drawable[] mDrawables;
private int mCurrentModuleIndex;
private MotionEvent mDown;
}
public void init() {
+ boolean landscape = Util.getDisplayRotation(this) % 180 == 90;
+ setMargins(landscape);
mControlsBackground = findViewById(R.id.blocker);
mCameraControls = findViewById(R.id.camera_controls);
mShutter = (ShutterButton) findViewById(R.id.shutter_button);
mSwitcher = (CameraSwitcher) findViewById(R.id.camera_switcher);
- mPieMenuButton = findViewById(R.id.menu_button);
- mSwitcherControl = findViewById(R.id.switcher_control);
+ mPieMenuButton = findViewById(R.id.menu);
int totaldrawid = (LightCycleHelper.hasLightCycleCapture(this)
? DRAW_IDS.length : DRAW_IDS.length - 1);
if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--;
mCurrentModule = LightCycleHelper.createPanoramaModule();
break;
}
- if (mCurrentModule.needsPieMenu()) {
- mPieMenuButton.setVisibility(View.VISIBLE);
- } else {
- mPieMenuButton.setVisibility(View.INVISIBLE);
- }
+ showPieMenuButton(mCurrentModule.needsPieMenu());
+
openModule(mCurrentModule, canReuse);
mCurrentModule.onOrientationChanged(mLastRawOrientation);
if (mMediaSaveService != null) {
getCameraScreenNail().setOnFrameDrawnOneShot(mOnFrameDrawn);
}
+ public void showPieMenuButton(boolean show) {
+ if (show) {
+ findViewById(R.id.blocker).setVisibility(View.VISIBLE);
+ findViewById(R.id.menu).setVisibility(View.VISIBLE);
+ findViewById(R.id.on_screen_indicators).setVisibility(View.VISIBLE);
+ } else {
+ findViewById(R.id.blocker).setVisibility(View.INVISIBLE);
+ findViewById(R.id.menu).setVisibility(View.INVISIBLE);
+ findViewById(R.id.on_screen_indicators).setVisibility(View.INVISIBLE);
+ }
+ }
+
private Runnable mOnFrameDrawn = new Runnable() {
@Override
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
+ boolean landscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setMargins(landscape);
+ mCurrentModule.onConfigurationChanged(config);
+ }
+ private void setMargins(boolean landscape) {
ViewGroup appRoot = (ViewGroup) findViewById(R.id.content);
- boolean landscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) appRoot.getLayoutParams();
+ int navBarWidth = getResources().getDimensionPixelSize(R.dimen.navigation_bar_width);
+ int navBarHeight = getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
if (landscape) {
- lp.rightMargin = getResources().getDimensionPixelSize(R.dimen.margin_systemui_offset);
+ lp.setMargins(navBarHeight, 0, navBarHeight - navBarWidth, 0);
} else {
- lp.rightMargin = 0;
+ lp.setMargins(0, navBarHeight, 0, 0);
}
appRoot.setLayoutParams(lp);
-
- // Reset the background after rotation
- mControlsBackground.setBackgroundResource(0); // remove the current background
- mControlsBackground.setBackgroundResource(R.drawable.switcher_bg);
-
- mCurrentModule.onConfigurationChanged(config);
}
@Override
}
if ((mSwitcher != null) && mSwitcher.showsPopup() && !mSwitcher.isInsidePopup(m)) {
return mSwitcher.onTouch(null, m);
+ } else if ((mSwitcher != null) && mSwitcher.isInsidePopup(m)) {
+ return superDispatchTouchEvent(m);
} else {
- return mSwitcherControl.dispatchTouchEvent(m)
- || mCurrentModule.dispatchTouchEvent(m);
+ return mCurrentModule.dispatchTouchEvent(m);
}
}
import android.view.View;
import android.view.ViewStub;
import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
}
};
+ private final View.OnLayoutChangeListener mLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ onScreenSizeChanged(right - left, bottom - top);
+ }
+ };
+ private int mPreviewWidth = 0;
+ private int mPreviewHeight = 0;
private final StringBuilder mBuilder = new StringBuilder();
private final Formatter mFormatter = new Formatter(mBuilder);
private final Object[] mFormatterArgs = new Object[1];
mActivity.getLayoutInflater().inflate(R.layout.photo_module,
(ViewGroup) mRootView, true);
+ mRootView.addOnLayoutChangeListener(mLayoutChangeListener);
if (ApiHelper.HAS_FACE_DETECTION) {
ViewStub faceViewStub = (ViewStub) mRootView
.findViewById(R.id.face_view_stub);
initializePhotoControl();
// These depend on camera parameters.
- int width = mActivity.getWindowManager().getDefaultDisplay().getWidth();
- int height = mActivity.getWindowManager().getDefaultDisplay().getHeight();
+ int width = mRootView.getWidth();
+ int height = mRootView.getHeight();
mFocusManager.setPreviewSize(width, height);
// Full-screen screennail
if (Util.getDisplayRotation(mActivity) % 180 == 0) {
onFullScreenChanged(mActivity.isInCameraApp());
}
+ public void onScreenSizeChanged(int width, int height) {
+ // Full-screen screennail
+ int w = width;
+ int h = height;
+ if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+ w = height;
+ h = width;
+ }
+ if (mPreviewWidth != w || mPreviewHeight != h) {
+ Log.d(TAG, "Preview size changed.");
+ if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+ ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(w, h);
+ }
+ }
+
private void initializePhotoControl() {
loadCameraPreferences();
if (mPhotoControl != null) {
mHandler.removeMessages(OPEN_CAMERA_FAIL);
mHandler.removeMessages(CAMERA_DISABLED);
+ mRootView.removeOnLayoutChangeListener(mLayoutChangeListener);
+ mPreviewWidth = 0;
+ mPreviewHeight = 0;
mPendingSwitchCameraId = -1;
if (mFocusManager != null) mFocusManager.removeMessages();
MediaSaveService s = mActivity.getMediaSaveService();
}
return result;
}
-
- @Override
- public void onConfigurationChanged(Configuration config) {
- super.onConfigurationChanged(config);
- RotatableLayout.rotate(this, config.orientation == Configuration.ORIENTATION_PORTRAIT);
- }
}
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout.LayoutParams;
import android.widget.LinearLayout;
+import com.android.camera.Util;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
(ViewGroup) getParent());
LinearLayout content = (LinearLayout) mParent.findViewById(R.id.content);
mPopup = content;
+ // Set the gravity of the popup, so that it shows up at the right position
+ // on screen
+ LayoutParams lp = ((LayoutParams) mPopup.getLayoutParams());
+ lp.gravity = ((LayoutParams) mParent.findViewById(R.id.camera_switcher)
+ .getLayoutParams()).gravity;
+ mPopup.setLayoutParams(lp);
+
mPopup.setVisibility(View.INVISIBLE);
mNeedsAnimationSetup = true;
for (int i = mDrawIds.length - 1; i >= 0; i--) {
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- onCameraSelected(index);
+ if (showsPopup()) onCameraSelected(index);
}
});
switch (mDrawIds[i]) {
public boolean isInsidePopup(MotionEvent evt) {
if (!showsPopup()) return false;
- return evt.getX() >= mPopup.getLeft()
- && evt.getX() < mPopup.getRight()
- && evt.getY() >= mPopup.getTop()
- && evt.getY() < mPopup.getBottom();
+ int topLeft[] = new int[2];
+ mPopup.getLocationOnScreen(topLeft);
+ int left = topLeft[0];
+ int top = topLeft[1];
+ int bottom = top + mPopup.getHeight();
+ int right = left + mPopup.getWidth();
+ return evt.getX() >= left && evt.getX() < right
+ && evt.getY() >= top && evt.getY() < bottom;
}
private void hidePopup() {
}
private void updateInitialTranslations() {
- if (getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT) {
+ int orientation = Util.getDisplayRotation((Activity) getContext());
+ if (orientation == 0) {
mTranslationX = -getWidth() / 2;
mTranslationY = getHeight();
- } else {
+ } else if (orientation == 90) {
mTranslationX = getWidth();
mTranslationY = getHeight() / 2;
+ } else if (orientation == 180) {
+ mTranslationX = getWidth();
+ mTranslationY = -getHeight() / 2;
+ } else {
+ mTranslationX = -getWidth();
+ mTranslationY = -getHeight() / 2;
}
}
+
private void popupAnimationSetup() {
if (!ApiHelper.HAS_VIEW_PROPERTY_ANIMATOR) {
return;
@Override
public void onAnimationEnd(Animator animation) {
// Verify that we weren't canceled
- if (!showsPopup()) {
+ if (!showsPopup() && mPopup != null) {
mPopup.setVisibility(View.INVISIBLE);
((ViewGroup) mParent).removeView(mPopup);
mPopup = null;
package com.android.camera.ui;
+import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import com.android.camera.Util;
+
/* RotatableLayout rotates itself as well as all its children when orientation
* changes. Specifically, when going from portrait to landscape, camera
* controls move from the bottom of the screen to right side of the screen
public class RotatableLayout extends FrameLayout {
+ private static final String TAG = "RotatableLayout";
+ private int mPrevRotation;
public RotatableLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
@Override
+ public void onFinishInflate() { // get initial orientation
+ mPrevRotation = Util.getDisplayRotation((Activity) getContext());
+ }
+
+ @Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
- // rotate the layout itself and all its children
- boolean clockwise = (config.orientation == Configuration.ORIENTATION_PORTRAIT);
- rotate(this, clockwise);
+ // Change the size of the layout
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ int width = lp.width;
+ int height = lp.height;
+ lp.height = width;
+ lp.width = height;
+ setLayoutParams(lp);
+ // rotate all the children
+ int rotation = Util.getDisplayRotation((Activity) getContext());
+ boolean clockwise = isClockWiseRotation(mPrevRotation, rotation);
+ mPrevRotation = rotation;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
}
}
+ public static boolean isClockWiseRotation(int prevRotation, int currentRotation) {
+ if (prevRotation == (currentRotation + 90) % 360) {
+ return true;
+ }
+ return false;
+ }
+
public static void rotate(View view, boolean isClockwise) {
if (isClockwise) {
rotateClockwise(view);
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper;
import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.UsageStatistics;
public class AbstractGalleryActivity extends Activity implements GalleryContext {
@SuppressWarnings("unused")
mPanoramaViewHelper = new PanoramaViewHelper(this);
mPanoramaViewHelper.onCreate();
doBindBatchService();
+ UsageStatistics.showOptInDialogIfNeeded(this);
}
@Override
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.LightCycleHelper;
import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.UsageStatistics;
import java.io.File;
GalleryUtils.initialize(this);
WidgetUtils.initialize(this);
PicasaSource.initialize(this);
+ UsageStatistics.initialize(this);
mStitchingProgressManager = LightCycleHelper.createStitchingManagerInstance(this);
if (mStitchingProgressManager != null) {
import com.android.gallery3d.anim.StateTransitionAnimation;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.UsageStatistics;
import java.util.Stack;
StateTransitionAnimation.Transition.Incoming);
if (mIsResumed) top.onPause();
}
+ UsageStatistics.onContentViewChanged(
+ UsageStatistics.COMPONENT_GALLERY,
+ klass.getSimpleName());
state.initialize(mActivity, data);
mStack.push(new StateEntry(data, state));
} else {
mResult = state.mResult;
}
-
+ UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+ klass.getSimpleName());
mStack.push(new StateEntry(data, state));
state.onCreate(data, null);
if (mIsResumed) state.resume();
state.onDestroy();
if (top != null && mIsResumed) top.resume();
+ if (top != null) {
+ UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+ top.getClass().getSimpleName());
+ }
}
public void switchState(ActivityState oldState,
mStack.push(new StateEntry(data, state));
state.onCreate(data, null);
if (mIsResumed) state.resume();
+ UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+ klass.getSimpleName());
}
public void destroy() {
public void restoreFromState(Bundle inState) {
Log.v(TAG, "restoreFromState");
Parcelable list[] = inState.getParcelableArray(KEY_MAIN);
+ ActivityState topState = null;
for (Parcelable parcelable : list) {
Bundle bundle = (Bundle) parcelable;
Class<? extends ActivityState> klass =
activityState.initialize(mActivity, data);
activityState.onCreate(data, state);
mStack.push(new StateEntry(data, activityState));
+ topState = activityState;
+ }
+ if (topState != null) {
+ UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+ topState.getClass().getSimpleName());
}
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.undoButton: {
+ mPanelController.resetParameters();
HistoryAdapter adapter = mMasterImage.getHistory();
int position = adapter.undo();
mMasterImage.onHistoryItemClick(position);
import android.graphics.Bitmap;
import android.support.v8.renderscript.*;
import android.util.Log;
+import com.android.gallery3d.R;
public abstract class ImageFilterRS extends ImageFilter {
private final String LOGTAG = "ImageFilterRS";
sOldBitmap = null;
}
+ public Allocation convertRGBAtoA(Bitmap bitmap) {
+ Type.Builder tb_a8 = new Type.Builder(mRS, Element.U8(mRS));
+ ScriptC_grey greyConvert = new ScriptC_grey(mRS, mResources, R.raw.grey);
+
+ Allocation bitmapTemp = Allocation.createFromBitmap(mRS, bitmap);
+
+ if (bitmapTemp.getType().getElement().isCompatible(Element.U8(mRS))) {
+ return bitmapTemp;
+ }
+
+ tb_a8.setX(bitmapTemp.getType().getX());
+ tb_a8.setY(bitmapTemp.getType().getY());
+ Allocation bitmapAlloc = Allocation.createTyped(mRS, tb_a8.create());
+ greyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
+
+ return bitmapAlloc;
+
+ }
+
}
--- /dev/null
+ /*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+
+uchar __attribute__((kernel)) RGBAtoA(uchar4 in) {
+ return in.r;
+}
package com.android.photos;
import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.CursorAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.photos.data.AlbumSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.shims.MediaSetLoader;
-public class AlbumSetFragment extends Fragment {
+import java.util.Date;
+
+public class AlbumSetFragment extends Fragment implements OnItemClickListener,
+ LoaderCallbacks<Cursor> {
+
+ private GridView mAlbumSetView;
+ private View mEmptyView;
+ private AlbumSetCursorAdapter mAdapter;
+
+ private static final int LOADER_ALBUMSET = 1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAdapter = new AlbumSetCursorAdapter(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.album_set, container, false);
+ mAlbumSetView = (GridView) root.findViewById(android.R.id.list);
+ mEmptyView = root.findViewById(android.R.id.empty);
+ mEmptyView.setVisibility(View.GONE);
+ mAlbumSetView.setAdapter(mAdapter);
+ mAlbumSetView.setOnItemClickListener(this);
+ getLoaderManager().initLoader(LOADER_ALBUMSET, null, this);
+ updateEmptyStatus();
+ return root;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // TODO: Switch to AlbumSetLoader
+ MediaSetLoader loader = new MediaSetLoader(getActivity());
+ mAdapter.setDrawableFactory(loader);
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader,
+ Cursor data) {
+ mAdapter.swapCursor(data);
+ updateEmptyStatus();
+ }
+
+ private void updateEmptyStatus() {
+ boolean empty = (mAdapter == null || mAdapter.getCount() == 0);
+ mAlbumSetView.setVisibility(empty ? View.GONE : View.VISIBLE);
+ mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> av, View v, int pos, long id) {
+ Cursor c = (Cursor) av.getItemAtPosition(pos);
+ int albumId = c.getInt(AlbumSetLoader.INDEX_ID);
+ // TODO launch an activity showing the photos in the album
+ Toast.makeText(v.getContext(), "Clicked " + albumId, Toast.LENGTH_SHORT).show();
+ }
+
+ private static class AlbumSetCursorAdapter extends CursorAdapter {
+
+ private LoaderCompatShim<Cursor> mDrawableFactory;
+
+ public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
+ mDrawableFactory = factory;
+ }
+ private Date mDate = new Date(); // Used for converting timestamps for display
+
+ public AlbumSetCursorAdapter(Context context) {
+ super(context, null, false);
+ }
+
+ @Override
+ public void bindView(View v, Context context, Cursor cursor) {
+ TextView titleTextView = (TextView) v.findViewById(
+ R.id.album_set_item_title);
+ titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE));
+
+ TextView dateTextView = (TextView) v.findViewById(
+ R.id.album_set_item_date);
+ long timestamp = cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP);
+ if (timestamp > 0) {
+ mDate.setTime(timestamp);
+ dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate));
+ } else {
+ dateTextView.setText(null);
+ }
+
+ ProgressBar uploadProgressBar = (ProgressBar) v.findViewById(
+ R.id.album_set_item_upload_progress);
+ if (cursor.getInt(AlbumSetLoader.INDEX_COUNT_PENDING_UPLOAD) > 0) {
+ uploadProgressBar.setVisibility(View.VISIBLE);
+ uploadProgressBar.setProgress(50);
+ } else {
+ uploadProgressBar.setVisibility(View.INVISIBLE);
+ }
+
+ ImageView thumbImageView = (ImageView) v.findViewById(
+ R.id.album_set_item_image);
+ Drawable recycle = thumbImageView.getDrawable();
+ Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+ if (recycle != drawable) {
+ thumbImageView.setImageDrawable(drawable);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return LayoutInflater.from(context).inflate(
+ R.layout.album_set_item, parent, false);
+ }
+ }
}
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.CursorAdapter;
+import android.widget.GridView;
import android.widget.ImageView;
import com.android.gallery3d.R;
+import com.android.gallery3d.app.Gallery;
import com.android.photos.data.PhotoSetLoader;
-import com.android.photos.drawables.DataUriThumbnailDrawable;
-import com.android.photos.views.GalleryThumbnailView;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.shims.MediaItemsLoader;
import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
-public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor> {
+public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor>,
+ OnItemClickListener {
private static final int LOADER_PHOTOSET = 1;
- private GalleryThumbnailView mPhotoSetView;
+ private GridView mPhotoSetView;
private View mEmptyView;
private ThumbnailAdapter mAdapter;
+ private boolean mInitialLoadComplete = false;
+ private LoaderCompatShim<Cursor> mLoaderCompatShim;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAdapter = new ThumbnailAdapter(getActivity());
+ }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.photo_set, container, false);
- mPhotoSetView = (GalleryThumbnailView) root.findViewById(android.R.id.list);
+ mPhotoSetView = (GridView) root.findViewById(android.R.id.list);
+ // TODO: Remove once UI stabilizes
+ mPhotoSetView.setColumnWidth(MediaItemsLoader.getThumbnailSize());
+ mPhotoSetView.setOnItemClickListener(this);
mEmptyView = root.findViewById(android.R.id.empty);
mEmptyView.setVisibility(View.GONE);
- mAdapter = new ThumbnailAdapter(getActivity());
mPhotoSetView.setAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_PHOTOSET, null, this);
updateEmptyStatus();
private void updateEmptyStatus() {
boolean empty = (mAdapter == null || mAdapter.getCount() == 0);
mPhotoSetView.setVisibility(empty ? View.GONE : View.VISIBLE);
- mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
+ mEmptyView.setVisibility(empty && mInitialLoadComplete
+ ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ if (mLoaderCompatShim == null) {
+ // Not fully initialized yet, discard
+ return;
+ }
+ Cursor item = mAdapter.getItem(position);
+ Uri uri = mLoaderCompatShim.uriForItem(item);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setClass(getActivity(), Gallery.class);
+ startActivity(intent);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return new PhotoSetLoader(getActivity());
+ // TODO: Switch to PhotoSetLoader
+ MediaItemsLoader loader = new MediaItemsLoader(getActivity());
+ mAdapter.setDrawableFactory(loader);
+ mInitialLoadComplete = false;
+ mLoaderCompatShim = loader;
+ return loader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader,
Cursor data) {
mAdapter.swapCursor(data);
+ mInitialLoadComplete = true;
updateEmptyStatus();
}
public void onLoaderReset(Loader<Cursor> loader) {
}
- private static class ShowFullScreen implements OnClickListener {
-
- @Override
- public void onClick(View view) {
- String path = (String) view.getTag();
- Intent intent = new Intent(view.getContext(), FullscreenViewer.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(path));
- view.getContext().startActivity(intent);
- }
-
- }
-
private static class ThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter {
- private static ShowFullScreen sShowFullscreenClickListener = new ShowFullScreen();
+ private LayoutInflater mInflater;
+ private LoaderCompatShim<Cursor> mDrawableFactory;
public ThumbnailAdapter(Context context) {
super(context, null, false);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
+ mDrawableFactory = factory;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView iv = (ImageView) view;
- DataUriThumbnailDrawable drawable = (DataUriThumbnailDrawable) iv.getDrawable();
- int width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
- int height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
- String path = cursor.getString(PhotoSetLoader.INDEX_DATA);
- drawable.setImage(path, width, height);
- iv.setTag(path);
+ Drawable recycle = iv.getDrawable();
+ Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+ if (recycle != drawable) {
+ iv.setImageDrawable(drawable);
+ }
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- ImageView iv = new ImageView(context);
- DataUriThumbnailDrawable drawable = new DataUriThumbnailDrawable();
- iv.setImageDrawable(drawable);
- int padding = (int) Math.ceil(2 * context.getResources().getDisplayMetrics().density);
- iv.setPadding(padding, padding, padding, padding);
- iv.setOnClickListener(sShowFullscreenClickListener);
- return iv;
+ View view = mInflater.inflate(R.layout.photo_set_item, parent, false);
+ LayoutParams params = view.getLayoutParams();
+ int columnWidth = ((GridView) parent).getColumnWidth();
+ params.height = columnWidth;
+ view.setLayoutParams(params);
+ return view;
}
@Override
--- /dev/null
+package com.android.photos.data;
+
+import android.database.MatrixCursor;
+
+
+public class AlbumSetLoader {
+ public static final int INDEX_ID = 0;
+ public static final int INDEX_TITLE = 1;
+ public static final int INDEX_TIMESTAMP = 2;
+ public static final int INDEX_THUMBNAIL_URI = 3;
+ public static final int INDEX_THUMBNAIL_WIDTH = 4;
+ public static final int INDEX_THUMBNAIL_HEIGHT = 5;
+ public static final int INDEX_COUNT_PENDING_UPLOAD = 6;
+ public static final int INDEX_COUNT = 7;
+
+ public static final String[] PROJECTION = {
+ "_id",
+ "title",
+ "timestamp",
+ "thumb_uri",
+ "thumb_width",
+ "thumb_height",
+ "count_pending_upload",
+ "_count"
+ };
+ public static final MatrixCursor MOCK = createRandomCursor(30);
+
+ private static MatrixCursor createRandomCursor(int count) {
+ MatrixCursor c = new MatrixCursor(PROJECTION, count);
+ for (int i = 0; i < count; i++) {
+ c.addRow(createRandomRow());
+ }
+ return c;
+ }
+
+ private static Object[] createRandomRow() {
+ double random = Math.random();
+ int id = (int) (500 * random);
+ Object[] row = {
+ id,
+ "Fun times " + id,
+ (long) (System.currentTimeMillis() * random),
+ null,
+ 0,
+ 0,
+ (random < .3 ? 1 : 0),
+ 1
+ };
+ return row;
+ }
+}
\ No newline at end of file
+++ /dev/null
-/*
- * 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.photos.data;
-
-import android.content.AsyncTaskLoader;
-import android.content.Context;
-
-import com.android.gallery3d.data.ContentListener;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.MediaSet.SyncListener;
-import com.android.gallery3d.util.Future;
-
-/**
- * Proof of concept, don't use
- */
-public class MediaSetLoader extends AsyncTaskLoader<MediaSet> {
-
- private static final SyncListener sNullListener = new SyncListener() {
- @Override
- public void onSyncDone(MediaSet mediaSet, int resultCode) {
- }
- };
-
- private MediaSet mMediaSet;
- private Future<Integer> mSyncTask = null;
- private ContentListener mObserver = new ContentListener() {
- @Override
- public void onContentDirty() {
- onContentChanged();
- }
- };
-
- public MediaSetLoader(Context context, String path) {
- super(context);
- mMediaSet = DataManager.from(getContext()).getMediaSet(path);
- }
-
- @Override
- protected void onStartLoading() {
- super.onStartLoading();
- mMediaSet.addContentListener(mObserver);
- mSyncTask = mMediaSet.requestSync(sNullListener);
- forceLoad();
- }
-
- @Override
- protected boolean onCancelLoad() {
- if (mSyncTask != null) {
- mSyncTask.cancel();
- mSyncTask = null;
- }
- return super.onCancelLoad();
- }
-
- @Override
- protected void onStopLoading() {
- super.onStopLoading();
- cancelLoad();
- mMediaSet.removeContentListener(mObserver);
- }
-
- @Override
- protected void onReset() {
- super.onReset();
- onStopLoading();
- }
-
- @Override
- public MediaSet loadInBackground() {
- mMediaSet.loadIfDirty();
- return mMediaSet;
- }
-
-}
import com.android.photos.data.PhotoProvider.ChangeNotification;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
/**
* Used for capturing notifications from PhotoProvider without relying on
* ContentObservers, so PhotoProvider allows this alternative for testing.
*/
public class NotificationWatcher implements ChangeNotification {
- private Set<Uri> mUris = new HashSet<Uri>();
+ private ArrayList<Uri> mUris = new ArrayList<Uri>();
+ private boolean mSyncToNetwork = false;
@Override
- public void notifyChange(Uri uri) {
+ public void notifyChange(Uri uri, boolean syncToNetwork) {
mUris.add(uri);
+ mSyncToNetwork = mSyncToNetwork || syncToNetwork;
}
public boolean isNotified(Uri uri) {
return mUris.size();
}
+ public boolean syncToNetwork() {
+ return mSyncToNetwork;
+ }
+
public void reset() {
mUris.clear();
+ mSyncToNetwork = false;
}
}
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import com.android.photos.data.PhotoProvider.Accounts;
import com.android.photos.data.PhotoProvider.Albums;
import com.android.photos.data.PhotoProvider.Metadata;
import com.android.photos.data.PhotoProvider.Photos;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Used in PhotoProvider to create and access the database containing
* information about photo and video information stored on the server.
private static final String[][] CREATE_PHOTO = {
{ Photos._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
- { Photos.SERVER_ID, "INTEGER UNIQUE" },
+ // Photos.ACCOUNT_ID is a foreign key to Accounts._ID
+ { Photos.ACCOUNT_ID, "INTEGER NOT NULL" },
{ Photos.WIDTH, "INTEGER NOT NULL" },
{ Photos.HEIGHT, "INTEGER NOT NULL" },
{ Photos.DATE_TAKEN, "INTEGER NOT NULL" },
// Photos.ALBUM_ID is a foreign key to Albums._ID
{ Photos.ALBUM_ID, "INTEGER" },
{ Photos.MIME_TYPE, "TEXT NOT NULL" },
+ { Photos.TITLE, "TEXT" },
+ { Photos.DATE_MODIFIED, "INTEGER" },
+ { Photos.ROTATION, "INTEGER" },
};
private static final String[][] CREATE_ALBUM = {
{ Albums._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
- // Albums.PARENT_ID is a foriegn key to Albums._ID
+ // Albums.ACCOUNT_ID is a foreign key to Accounts._ID
+ { Albums.ACCOUNT_ID, "INTEGER NOT NULL" },
+ // Albums.PARENT_ID is a foreign key to Albums._ID
{ Albums.PARENT_ID, "INTEGER" },
- { Albums.NAME, "Text NOT NULL" },
{ Albums.VISIBILITY, "INTEGER NOT NULL" },
- { Albums.SERVER_ID, "INTEGER UNIQUE" },
- createUniqueConstraint(Albums.PARENT_ID, Albums.NAME),
+ { Albums.LOCATION_STRING, "TEXT" },
+ { Albums.TITLE, "TEXT NOT NULL" },
+ { Albums.SUMMARY, "TEXT" },
+ { Albums.DATE_PUBLISHED, "INTEGER" },
+ { Albums.DATE_MODIFIED, "INTEGER" },
+ createUniqueConstraint(Albums.PARENT_ID, Albums.TITLE),
};
private static final String[][] CREATE_METADATA = {
createUniqueConstraint(Metadata.PHOTO_ID, Metadata.KEY),
};
+ private static final String[][] CREATE_ACCOUNT = {
+ { Accounts._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ { Accounts.ACCOUNT_NAME, "TEXT NOT NULL" },
+ };
+
@Override
public void onCreate(SQLiteDatabase db) {
- createTable(db, Albums.TABLE, CREATE_ALBUM);
- createTable(db, Photos.TABLE, CREATE_PHOTO);
- createTable(db, Metadata.TABLE, CREATE_METADATA);
+ createTable(db, Accounts.TABLE, getAccountTableDefinition());
+ createTable(db, Albums.TABLE, getAlbumTableDefinition());
+ createTable(db, Photos.TABLE, getPhotoTableDefinition());
+ createTable(db, Metadata.TABLE, getMetadataTableDefinition());
}
public PhotoDatabase(Context context, String dbName) {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
- protected static void createTable(SQLiteDatabase db, String table, String[][] columns) {
+ protected List<String[]> getAlbumTableDefinition() {
+ return tableCreationStrings(CREATE_ALBUM);
+ }
+
+ protected List<String[]> getPhotoTableDefinition() {
+ return tableCreationStrings(CREATE_PHOTO);
+ }
+
+ protected List<String[]> getMetadataTableDefinition() {
+ return tableCreationStrings(CREATE_METADATA);
+ }
+
+ protected List<String[]> getAccountTableDefinition() {
+ return tableCreationStrings(CREATE_ACCOUNT);
+ }
+
+ protected static void createTable(SQLiteDatabase db, String table, List<String[]> columns) {
StringBuilder create = new StringBuilder(SQL_CREATE_TABLE);
create.append(table).append('(');
boolean first = true;
"UNIQUE(", column1, ",", column2, ")"
};
}
+
+ protected static List<String[]> tableCreationStrings(String[][] createTable) {
+ ArrayList<String[]> create = new ArrayList<String[]>(createTable.length);
+ for (String[] line: createTable) {
+ create.add(line);
+ }
+ return create;
+ }
+
+ protected static void addToTable(List<String[]> createTable, String[][] columns, String[][] constraints) {
+ if (columns != null) {
+ for (String[] column: columns) {
+ createTable.add(0, column);
+ }
+ }
+ if (constraints != null) {
+ for (String[] constraint: constraints) {
+ createTable.add(constraint);
+ }
+ }
+ }
}
*/
package com.android.photos.data;
-import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
+import android.media.ExifInterface;
import android.net.Uri;
import android.os.CancellationSignal;
import android.provider.BaseColumns;
-import java.util.ArrayList;
import java.util.List;
/**
* in the selection. The selection and selectionArgs are not used when updating
* metadata. If the metadata values are null, the row will be deleted.
*/
-public class PhotoProvider extends ContentProvider {
+public class PhotoProvider extends SQLiteContentProvider {
@SuppressWarnings("unused")
private static final String TAG = PhotoProvider.class.getSimpleName();
// Used to allow mocking out the change notification because
// MockContextResolver disallows system-wide notification.
public static interface ChangeNotification {
- void notifyChange(Uri uri);
+ void notifyChange(Uri uri, boolean syncToNetwork);
}
/**
- * Contains columns that can be accessed via PHOTOS_CONTENT_URI.
+ * Contains columns that can be accessed via Accounts.CONTENT_URI
*/
- public static interface Photos extends BaseColumns {
+ public static interface Accounts extends BaseColumns {
/**
- * Internal database table used for basic photo information.
+ * Internal database table used for account information
*/
- public static final String TABLE = "photo";
+ public static final String TABLE = "accounts";
/**
- * Content URI for basic photo and video information.
+ * Content URI for account information
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
/**
- * Identifier used on the server. Long value.
- */
- public static final String SERVER_ID = "server_id";
- /**
- * Column name for the width of the original image. Integer value.
+ * User name for this account.
*/
+ public static final String ACCOUNT_NAME = "name";
+ }
+
+ /**
+ * Contains columns that can be accessed via Photos.CONTENT_URI.
+ */
+ public static interface Photos extends BaseColumns {
+ /** Internal database table used for basic photo information. */
+ public static final String TABLE = "photo";
+ /** Content URI for basic photo and video information. */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+
+ /** Long foreign key to Accounts._ID */
+ public static final String ACCOUNT_ID = "account_id";
+ /** Column name for the width of the original image. Integer value. */
public static final String WIDTH = "width";
- /**
- * Column name for the height of the original image. Integer value.
- */
+ /** Column name for the height of the original image. Integer value. */
public static final String HEIGHT = "height";
/**
* Column name for the date that the original image was taken. Long
* server.
*/
public static final String ALBUM_ID = "album_id";
+ /** The column name for the mime-type String. */
+ public static final String MIME_TYPE = "mime_type";
+ /** The title of the photo. String value. */
+ public static final String TITLE = "title";
+ /** The date the photo entry was last updated. Long value. */
+ public static final String DATE_MODIFIED = "date_modified";
/**
- * The column name for the mime-type String.
+ * The rotation of the photo in degrees, if rotation has not already
+ * been applied. Integer value.
*/
- public static final String MIME_TYPE = "mime_type";
+ public static final String ROTATION = "rotation";
}
/**
* Contains columns and Uri for accessing album information.
*/
public static interface Albums extends BaseColumns {
- /**
- * Internal database table used album information.
- */
+ /** Internal database table used album information. */
public static final String TABLE = "album";
- /**
- * Content URI for album information.
- */
+ /** Content URI for album information. */
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
- /**
- * Parent directory or null if this is in the root.
- */
- public static final String PARENT_ID = "parent";
- /**
- * Column name for the name of the album. String value.
- */
- public static final String NAME = "name";
+
+ /** Long foreign key to Accounts._ID */
+ public static final String ACCOUNT_ID = "account_id";
+ /** Parent directory or null if this is in the root. */
+ public static final String PARENT_ID = "parent_id";
/**
* Column name for the visibility level of the album. Can be any of the
* VISIBILITY_* values.
*/
public static final String VISIBILITY = "visibility";
- /**
- * Column name for the server identifier for this album. NULL if the
- * server doesn't have this album yet.
- */
- public static final String SERVER_ID = "server_id";
+ /** The user-specified location associated with the album. String value. */
+ public static final String LOCATION_STRING = "location_string";
+ /** The title of the album. String value. */
+ public static final String TITLE = "title";
+ /** A short summary of the contents of the album. String value. */
+ public static final String SUMMARY = "summary";
+ /** The date the album was created. Long value */
+ public static final String DATE_PUBLISHED = "date_published";
+ /** The date the album entry was last updated. Long value. */
+ public static final String DATE_MODIFIED = "date_modified";
// Privacy values for Albums.VISIBILITY
public static final int VISIBILITY_PRIVATE = 1;
* Contains columns and Uri for accessing photo and video metadata
*/
public static interface Metadata extends BaseColumns {
- /**
- * Internal database table used metadata information.
- */
+ /** Internal database table used metadata information. */
public static final String TABLE = "metadata";
- /**
- * Content URI for photo and video metadata.
- */
+ /** Content URI for photo and video metadata. */
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
- /**
- * Foreign key to photo_id. Long value.
- */
+ /** Foreign key to photo_id. Long value. */
public static final String PHOTO_ID = "photo_id";
- /**
- * Metadata key. String value
- */
+ /** Metadata key. String value */
public static final String KEY = "key";
/**
* Metadata value. Type is based on key.
*/
public static final String VALUE = "value";
+
+ /** A short summary of the photo. String value. */
+ public static final String KEY_SUMMARY = "summary";
+ /** The date the photo was added. Long value. */
+ public static final String KEY_PUBLISHED = "date_published";
+ /** The date the photo was last updated. Long value. */
+ public static final String KEY_DATE_UPDATED = "date_updated";
+ /** The size of the photo is bytes. Integer value. */
+ public static final String KEY_SIZE_IN_BTYES = "size";
+ /** The latitude associated with the photo. Double value. */
+ public static final String KEY_LATITUDE = "latitude";
+ /** The longitude associated with the photo. Double value. */
+ public static final String KEY_LONGITUDE = "longitude";
+
+ /** The make of the camera used. String value. */
+ public static final String KEY_EXIF_MAKE = ExifInterface.TAG_MAKE;
+ /** The model of the camera used. String value. */
+ public static final String KEY_EXIF_MODEL = ExifInterface.TAG_MODEL;;
+ /** The exposure time used. Float value. */
+ public static final String KEY_EXIF_EXPOSURE = ExifInterface.TAG_EXPOSURE_TIME;
+ /** Whether the flash was used. Boolean value. */
+ public static final String KEY_EXIF_FLASH = ExifInterface.TAG_FLASH;
+ /** The focal length used. Float value. */
+ public static final String KEY_EXIF_FOCAL_LENGTH = ExifInterface.TAG_FOCAL_LENGTH;
+ /** The fstop value used. Float value. */
+ public static final String KEY_EXIF_FSTOP = ExifInterface.TAG_APERTURE;
+ /** The ISO equivalent value used. Integer value. */
+ public static final String KEY_EXIF_ISO = ExifInterface.TAG_ISO;
}
/**
* Contains columns and Uri for maintaining the image cache.
*/
public static interface ImageCache extends BaseColumns {
- /**
- * Internal database table used for the image cache
- */
+ /** Internal database table used for the image cache */
public static final String TABLE = "image_cache";
/**
public static final String IMAGE_TYPE_QUERY_PARAMETER = "image_type";
// ImageCache.IMAGE_TYPE values
- public static final int IMAGE_TYPE_THUMBNAIL = 1;
- public static final int IMAGE_TYPE_PREVIEW = 2;
- public static final int IMAGE_TYPE_ORIGINAL = 3;
+ public static final int IMAGE_TYPE_ALBUM_COVER = 1;
+ public static final int IMAGE_TYPE_THUMBNAIL = 2;
+ public static final int IMAGE_TYPE_PREVIEW = 3;
+ public static final int IMAGE_TYPE_ORIGINAL = 4;
/**
* Content URI for retrieving image paths. The
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
/**
- * Foreign key to the photos._id. Long value.
+ * Content URI for retrieving the album cover art. The album ID must be
+ * appended to the URI.
*/
- public static final String PHOTO_ID = "photo_id";
+ public static final Uri ALBUM_COVER_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI,
+ Albums.TABLE);
+
/**
- * One of IMAGE_TYPE_* values.
+ * An _ID from Albums or Photos, depending on whether IMAGE_TYPE is
+ * IMAGE_TYPE_ALBUM or not. Long value.
*/
+ public static final String REMOTE_ID = "remote_id";
+ /** One of IMAGE_TYPE_* values. */
public static final String IMAGE_TYPE = "image_type";
- /**
- * The String path to the image.
- */
+ /** The String path to the image. */
public static final String PATH = "path";
};
};
protected ChangeNotification mNotifier = null;
- private SQLiteOpenHelper mOpenHelper;
protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
protected static final int MATCH_PHOTO = 1;
protected static final int MATCH_METADATA = 5;
protected static final int MATCH_METADATA_ID = 6;
protected static final int MATCH_IMAGE = 7;
+ protected static final int MATCH_ALBUM_COVER = 8;
static {
sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO);
sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID);
// match against image_cache/<ImageCache.PHOTO_ID>
sUriMatcher.addURI(AUTHORITY, ImageCache.TABLE + "/#", MATCH_IMAGE);
+ // match against image_cache/album/<Albums._ID>
+ sUriMatcher.addURI(AUTHORITY, ImageCache.TABLE + "/" + Albums.TABLE + "/#",
+ MATCH_ALBUM_COVER);
}
@Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
+ public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter) {
int match = matchUri(uri);
- if (match == MATCH_IMAGE) {
- throw new IllegalArgumentException("Cannot delete from image cache");
- }
selection = addIdToSelection(match, selection);
selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
- List<Uri> changeUris = new ArrayList<Uri>();
int deleted = 0;
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- deleted = deleteCascade(db, match, selection, selectionArgs, changeUris, uri);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- for (Uri changeUri : changeUris) {
- notifyChanges(changeUri);
- }
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ deleted = deleteCascade(db, match, selection, selectionArgs, uri);
return deleted;
}
}
@Override
- public Uri insert(Uri uri, ContentValues values) {
- // Cannot insert into this ContentProvider
- return null;
- }
-
- @Override
- public boolean onCreate() {
- mOpenHelper = createDatabaseHelper();
- return true;
- }
-
- @Override
- public void shutdown() {
- getDatabaseHelper().close();
+ public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
+ int match = matchUri(uri);
+ validateMatchTable(match);
+ String table = getTableFromMatch(match, uri);
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ Uri insertedUri = null;
+ long id = db.insert(table, null, values);
+ if (id != -1) {
+ // uri already matches the table.
+ insertedUri = ContentUris.withAppendedId(uri, id);
+ postNotifyUri(insertedUri);
+ }
+ return insertedUri;
}
@Override
selection = addIdToSelection(match, selection);
selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
String table = getTableFromMatch(match, uri);
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
return db.query(false, table, projection, selection, selectionArgs, null, null, sortOrder,
null, cancellationSignal);
}
@Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ public int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
int match = matchUri(uri);
int rowsUpdated = 0;
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- if (match == MATCH_METADATA) {
- rowsUpdated = modifyMetadata(db, values);
- } else {
- selection = addIdToSelection(match, selection);
- selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
- String table = getTableFromMatch(match, uri);
- rowsUpdated = db.update(table, values, selection, selectionArgs);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ if (match == MATCH_METADATA) {
+ rowsUpdated = modifyMetadata(db, values);
+ } else {
+ selection = addIdToSelection(match, selection);
+ selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+ String table = getTableFromMatch(match, uri);
+ rowsUpdated = db.update(table, values, selection, selectionArgs);
}
- notifyChanges(uri);
+ postNotifyUri(uri);
return rowsUpdated;
}
return table;
}
- protected final SQLiteOpenHelper getDatabaseHelper() {
- return mOpenHelper;
- }
-
- protected SQLiteOpenHelper createDatabaseHelper() {
- return new PhotoDatabase(getContext(), DB_NAME);
+ @Override
+ public SQLiteOpenHelper getDatabaseHelper(Context context) {
+ return new PhotoDatabase(context, DB_NAME);
}
private int modifyMetadata(SQLiteDatabase db, ContentValues values) {
- String[] selectionArgs = {
- values.getAsString(Metadata.PHOTO_ID),
- values.getAsString(Metadata.KEY),
- };
int rowCount;
if (values.get(Metadata.VALUE) == null) {
+ String[] selectionArgs = {
+ values.getAsString(Metadata.PHOTO_ID), values.getAsString(Metadata.KEY),
+ };
rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs);
} else {
- rowCount = (int) DatabaseUtils.queryNumEntries(db, Metadata.TABLE, WHERE_METADATA_ID,
- selectionArgs);
- if (rowCount > 0) {
- db.update(Metadata.TABLE, values, WHERE_METADATA_ID, selectionArgs);
- } else {
- db.insert(Metadata.TABLE, null, values);
- rowCount = 1;
- }
+ long rowId = db.replace(Metadata.TABLE, null, values);
+ rowCount = (rowId == -1) ? 0 : 1;
}
return rowCount;
}
if (match == UriMatcher.NO_MATCH) {
throw unknownUri(uri);
}
+ if (match == MATCH_IMAGE || match == MATCH_ALBUM_COVER) {
+ throw new IllegalArgumentException("Operation not allowed on image cache database");
+ }
return match;
}
- protected void notifyChanges(Uri uri) {
+ @Override
+ protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
if (mNotifier != null) {
- mNotifier.notifyChange(uri);
+ mNotifier.notifyChange(uri, syncToNetwork);
} else {
- getContext().getContentResolver().notifyChange(uri, null, false);
+ resolver.notifyChange(uri, null, syncToNetwork);
}
}
return matchColumn + IN + NESTED_SELECT_START + query + NESTED_SELECT_END;
}
- protected static int deleteCascade(SQLiteDatabase db, int match, String selection,
- String[] selectionArgs, List<Uri> changeUris, Uri uri) {
+ protected int deleteCascade(SQLiteDatabase db, int match, String selection,
+ String[] selectionArgs, Uri uri) {
switch (match) {
case MATCH_PHOTO:
case MATCH_PHOTO_ID: {
- deleteCascadeMetadata(db, selection, selectionArgs, changeUris);
+ deleteCascadeMetadata(db, selection, selectionArgs);
break;
}
case MATCH_ALBUM:
case MATCH_ALBUM_ID: {
- deleteCascadePhotos(db, selection, selectionArgs, changeUris);
+ deleteCascadePhotos(db, selection, selectionArgs);
break;
}
}
String table = getTableFromMatch(match, uri);
int deleted = db.delete(table, selection, selectionArgs);
if (deleted > 0) {
- changeUris.add(uri);
+ postNotifyUri(uri);
}
return deleted;
}
- private static void deleteCascadePhotos(SQLiteDatabase db, String albumSelect,
- String[] selectArgs, List<Uri> changeUris) {
+ private void deleteCascadePhotos(SQLiteDatabase db, String albumSelect,
+ String[] selectArgs) {
String photoWhere = nestWhere(Photos.ALBUM_ID, Albums.TABLE, albumSelect);
- deleteCascadeMetadata(db, photoWhere, selectArgs, changeUris);
+ deleteCascadeMetadata(db, photoWhere, selectArgs);
int deleted = db.delete(Photos.TABLE, photoWhere, selectArgs);
if (deleted > 0) {
- changeUris.add(Photos.CONTENT_URI);
+ postNotifyUri(Photos.CONTENT_URI);
}
}
- private static void deleteCascadeMetadata(SQLiteDatabase db, String photosSelect,
- String[] selectArgs, List<Uri> changeUris) {
+ private void deleteCascadeMetadata(SQLiteDatabase db, String photosSelect,
+ String[] selectArgs) {
String metadataWhere = nestWhere(Metadata.PHOTO_ID, Photos.TABLE, photosSelect);
int deleted = db.delete(Metadata.TABLE, metadataWhere, selectArgs);
if (deleted > 0) {
- changeUris.add(Metadata.CONTENT_URI);
+ postNotifyUri(Metadata.CONTENT_URI);
+ }
+ }
+
+ private static void validateMatchTable(int match) {
+ switch (match) {
+ case MATCH_PHOTO:
+ case MATCH_ALBUM:
+ case MATCH_METADATA:
+ break;
+ default:
+ throw new IllegalArgumentException("Operation not allowed on an existing row.");
}
}
}
import android.content.Context;
import android.content.CursorLoader;
import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Files.FileColumns;
-public class PhotoSetLoader extends CursorLoader {
+import com.android.photos.drawables.DataUriThumbnailDrawable;
+import com.android.photos.shims.LoaderCompatShim;
+
+public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> {
private static final Uri CONTENT_URI = Files.getContentUri("external");
- private static final String[] PROJECTION = new String[] {
+ public static final String[] PROJECTION = new String[] {
FileColumns._ID,
FileColumns.DATA,
FileColumns.WIDTH,
super.onReset();
getContext().getContentResolver().unregisterContentObserver(mGlobalObserver);
}
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ DataUriThumbnailDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof DataUriThumbnailDrawable)) {
+ drawable = new DataUriThumbnailDrawable();
+ } else {
+ drawable = (DataUriThumbnailDrawable) recycle;
+ }
+ drawable.setImage(item.getString(INDEX_DATA),
+ item.getInt(INDEX_WIDTH), item.getInt(INDEX_HEIGHT));
+ return drawable;
+ }
+
+ @Override
+ public Uri uriForItem(Cursor item) {
+ return null;
+ }
}
--- /dev/null
+/*
+ * 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.photos.data;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * General purpose {@link ContentProvider} base class that uses SQLiteDatabase
+ * for storage.
+ */
+public abstract class SQLiteContentProvider extends ContentProvider {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "SQLiteContentProvider";
+
+ private SQLiteOpenHelper mOpenHelper;
+ private Set<Uri> mChangedUris;
+
+ private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
+ private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
+
+ /**
+ * Maximum number of operations allowed in a batch between yield points.
+ */
+ private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
+
+ @Override
+ public boolean onCreate() {
+ Context context = getContext();
+ mOpenHelper = getDatabaseHelper(context);
+ mChangedUris = new HashSet<Uri>();
+ return true;
+ }
+
+ @Override
+ public void shutdown() {
+ getDatabaseHelper().close();
+ }
+
+ /**
+ * Returns a {@link SQLiteOpenHelper} that can open the database.
+ */
+ public abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+
+ /**
+ * The equivalent of the {@link #insert} method, but invoked within a
+ * transaction.
+ */
+ public abstract Uri insertInTransaction(Uri uri, ContentValues values,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #update} method, but invoked within a
+ * transaction.
+ */
+ public abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #delete} method, but invoked within a
+ * transaction.
+ */
+ public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * Call this to add a URI to the list of URIs to be notified when the
+ * transaction is committed.
+ */
+ protected void postNotifyUri(Uri uri) {
+ synchronized (mChangedUris) {
+ mChangedUris.add(uri);
+ }
+ }
+
+ public boolean isCallerSyncAdapter(Uri uri) {
+ return false;
+ }
+
+ public SQLiteOpenHelper getDatabaseHelper() {
+ return mOpenHelper;
+ }
+
+ private boolean applyingBatch() {
+ return mApplyingBatch.get() != null && mApplyingBatch.get();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Uri result = null;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ }
+ return result;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ int numValues = values.length;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ for (int i = 0; i < numValues; i++) {
+ @SuppressWarnings("unused")
+ Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
+ db.yieldIfContendedSafely();
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ return numValues;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ count = updateInTransaction(uri, values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
+ }
+
+ return count;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ }
+ return count;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ int ypCount = 0;
+ int opCount = 0;
+ boolean callerIsSyncAdapter = false;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ mApplyingBatch.set(true);
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+ throw new OperationApplicationException(
+ "Too many content provider operations between yield points. "
+ + "The maximum number of operations per yield point is "
+ + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
+ }
+ final ContentProviderOperation operation = operations.get(i);
+ if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) {
+ callerIsSyncAdapter = true;
+ }
+ if (i > 0 && operation.isYieldAllowed()) {
+ opCount = 0;
+ if (db.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
+ ypCount++;
+ }
+ }
+ results[i] = operation.apply(this, results, i);
+ }
+ db.setTransactionSuccessful();
+ return results;
+ } finally {
+ mApplyingBatch.set(false);
+ db.endTransaction();
+ onEndTransaction(callerIsSyncAdapter);
+ }
+ }
+
+ protected void onEndTransaction(boolean callerIsSyncAdapter) {
+ Set<Uri> changed;
+ synchronized (mChangedUris) {
+ changed = new HashSet<Uri>(mChangedUris);
+ mChangedUris.clear();
+ }
+ ContentResolver resolver = getContext().getContentResolver();
+ for (Uri uri : changed) {
+ boolean syncToNetwork = !callerIsSyncAdapter && syncToNetwork(uri);
+ notifyChange(resolver, uri, syncToNetwork);
+ }
+ }
+
+ protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
+ resolver.notifyChange(uri, null, syncToNetwork);
+ }
+
+ protected boolean syncToNetwork(Uri uri) {
+ return false;
+ }
+}
\ No newline at end of file
}
private void refreshSampleSizeLocked() {
- if (mBounds.isEmpty()) return;
+ if (mBounds.isEmpty() || mImageWidth == 0 || mImageHeight == 0) {
+ return;
+ }
int sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight);
if (sampleSize != mSampleSize || mBitmap == null) {
--- /dev/null
+package com.android.photos.shims;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.ui.BitmapLoader;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.photos.data.GalleryBitmapPool;
+
+
+public class BitmapJobDrawable extends Drawable implements Runnable {
+
+ private ThumbnailLoader mLoader;
+ private MediaItem mItem;
+ private Bitmap mBitmap;
+ private Paint mPaint = new Paint();
+ private Matrix mDrawMatrix = new Matrix();
+ private int mRotation = 0;
+
+ public BitmapJobDrawable() {
+ }
+
+ public void setMediaItem(MediaItem item) {
+ if (mItem == item) return;
+
+ if (mLoader != null) {
+ mLoader.cancelLoad();
+ }
+ mItem = item;
+ if (mBitmap != null) {
+ GalleryBitmapPool.getInstance().put(mBitmap);
+ mBitmap = null;
+ }
+ if (mItem != null) {
+ // TODO: Figure out why ThumbnailLoader doesn't like to be re-used
+ mLoader = new ThumbnailLoader(this);
+ mLoader.startLoad();
+ mRotation = mItem.getRotation();
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void run() {
+ Bitmap bitmap = mLoader.getBitmap();
+ if (bitmap != null) {
+ mBitmap = bitmap;
+ updateDrawMatrix();
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ updateDrawMatrix();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ if (mBitmap != null) {
+ canvas.save();
+ canvas.clipRect(bounds);
+ canvas.concat(mDrawMatrix);
+ canvas.rotate(mRotation, bounds.centerX(), bounds.centerY());
+ canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+ canvas.restore();
+ } else {
+ mPaint.setColor(0xFFCCCCCC);
+ canvas.drawRect(bounds, mPaint);
+ }
+ }
+
+ private void updateDrawMatrix() {
+ Rect bounds = getBounds();
+ if (mBitmap == null || bounds.isEmpty()) {
+ mDrawMatrix.reset();
+ return;
+ }
+
+ float scale;
+ float dx = 0, dy = 0;
+
+ int dwidth = mBitmap.getWidth();
+ int dheight = mBitmap.getHeight();
+ int vwidth = bounds.width();
+ int vheight = bounds.height();
+
+ // Calculates a matrix similar to ScaleType.CENTER_CROP
+ if (dwidth * vheight > vwidth * dheight) {
+ scale = (float) vheight / (float) dheight;
+ dx = (vwidth - dwidth * scale) * 0.5f;
+ } else {
+ scale = (float) vwidth / (float) dwidth;
+ dy = (vheight - dheight * scale) * 0.5f;
+ }
+
+ mDrawMatrix.setScale(scale, scale);
+ mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+ invalidateSelf();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public int getOpacity() {
+ Bitmap bm = mBitmap;
+ return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
+ PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ int oldAlpha = mPaint.getAlpha();
+ if (alpha != oldAlpha) {
+ mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ invalidateSelf();
+ }
+
+ private static class ThumbnailLoader extends BitmapLoader {
+ private static final ThreadPool sThreadPool = new ThreadPool(0, 2);
+ private BitmapJobDrawable mParent;
+
+ public ThumbnailLoader(BitmapJobDrawable parent) {
+ mParent = parent;
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return sThreadPool.submit(
+ mParent.mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mParent.scheduleSelf(mParent, 0);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.photos.shims;
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+
+public interface LoaderCompatShim<T> {
+ Drawable drawableForItem(T item, Drawable recycle);
+ Uri uriForItem(T item);
+}
--- /dev/null
+/*
+ * 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore.Files.FileColumns;
+import android.util.SparseArray;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.ItemConsumer;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.PhotoSetLoader;
+
+/**
+ * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear
+ * like a PhotoSetLoader
+ */
+public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderCompatShim<Cursor> {
+
+ private static final SyncListener sNullListener = new SyncListener() {
+ @Override
+ public void onSyncDone(MediaSet mediaSet, int resultCode) {
+ }
+ };
+
+ private final MediaSet mMediaSet;
+ private Future<Integer> mSyncTask = null;
+ private ContentListener mObserver = new ContentListener() {
+ @Override
+ public void onContentDirty() {
+ onContentChanged();
+ }
+ };
+ private SparseArray<MediaItem> mMediaItems;
+
+ public MediaItemsLoader(Context context) {
+ super(context);
+ DataManager dm = DataManager.from(context);
+ String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = dm.getMediaSet(path);
+ }
+
+ public MediaItemsLoader(Context context, String parentPath) {
+ super(context);
+ mMediaSet = DataManager.from(getContext()).getMediaSet(parentPath);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ mMediaSet.addContentListener(mObserver);
+ mSyncTask = mMediaSet.requestSync(sNullListener);
+ forceLoad();
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (mSyncTask != null) {
+ mSyncTask.cancel();
+ mSyncTask = null;
+ }
+ return super.onCancelLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ mMediaSet.removeContentListener(mObserver);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ onStopLoading();
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ mMediaSet.loadIfDirty();
+ final MatrixCursor cursor = new MatrixCursor(PhotoSetLoader.PROJECTION);
+ final Object[] row = new Object[PhotoSetLoader.PROJECTION.length];
+ final SparseArray<MediaItem> mediaItems = new SparseArray<MediaItem>();
+ mMediaSet.enumerateTotalMediaItems(new ItemConsumer() {
+ @Override
+ public void consume(int index, MediaItem item) {
+ row[PhotoSetLoader.INDEX_ID] = index;
+ row[PhotoSetLoader.INDEX_DATA] = item.getContentUri().toString();
+ row[PhotoSetLoader.INDEX_DATE_ADDED] = item.getDateInMs();
+ row[PhotoSetLoader.INDEX_HEIGHT] = item.getHeight();
+ row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+ row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+ int rawMediaType = item.getMediaType();
+ int mappedMediaType = FileColumns.MEDIA_TYPE_NONE;
+ if (rawMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
+ mappedMediaType = FileColumns.MEDIA_TYPE_IMAGE;
+ } else if (rawMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
+ mappedMediaType = FileColumns.MEDIA_TYPE_VIDEO;
+ }
+ row[PhotoSetLoader.INDEX_MEDIA_TYPE] = mappedMediaType;
+ cursor.addRow(row);
+ mediaItems.append(index, item);
+ }
+ });
+ synchronized (mMediaSet) {
+ mMediaItems = mediaItems;
+ }
+ return cursor;
+ }
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ BitmapJobDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+ drawable = new BitmapJobDrawable();
+ } else {
+ drawable = (BitmapJobDrawable) recycle;
+ }
+ int index = item.getInt(PhotoSetLoader.INDEX_ID);
+ drawable.setMediaItem(mMediaItems.get(index));
+ return drawable;
+ }
+
+ public static int getThumbnailSize() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public Uri uriForItem(Cursor item) {
+ int index = item.getInt(PhotoSetLoader.INDEX_ID);
+ MediaItem mi = mMediaItems.get(index);
+ return mi == null ? null : mi.getContentUri();
+ }
+
+}
--- /dev/null
+/*
+ * 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.AlbumSetLoader;
+
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaSets in a MediaSet, wrapping them in a cursor to appear
+ * like a AlbumSetLoader.
+ */
+public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCompatShim<Cursor>{
+
+ private static final SyncListener sNullListener = new SyncListener() {
+ @Override
+ public void onSyncDone(MediaSet mediaSet, int resultCode) {
+ }
+ };
+
+ private final MediaSet mMediaSet;
+ private Future<Integer> mSyncTask = null;
+ private ContentListener mObserver = new ContentListener() {
+ @Override
+ public void onContentDirty() {
+ onContentChanged();
+ }
+ };
+
+ private ArrayList<MediaItem> mCoverItems;
+
+ public MediaSetLoader(Context context) {
+ super(context);
+ DataManager dm = DataManager.from(context);
+ String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = dm.getMediaSet(path);
+ }
+
+ public MediaSetLoader(Context context, String path) {
+ super(context);
+ mMediaSet = DataManager.from(getContext()).getMediaSet(path);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ mMediaSet.addContentListener(mObserver);
+ mSyncTask = mMediaSet.requestSync(sNullListener);
+ forceLoad();
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (mSyncTask != null) {
+ mSyncTask.cancel();
+ mSyncTask = null;
+ }
+ return super.onCancelLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ mMediaSet.removeContentListener(mObserver);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ onStopLoading();
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ mMediaSet.loadIfDirty();
+ final MatrixCursor cursor = new MatrixCursor(AlbumSetLoader.PROJECTION);
+ final Object[] row = new Object[AlbumSetLoader.PROJECTION.length];
+ int count = mMediaSet.getSubMediaSetCount();
+ ArrayList<MediaItem> coverItems = new ArrayList<MediaItem>(count);
+ for (int i = 0; i < count; i++) {
+ MediaSet m = mMediaSet.getSubMediaSet(i);
+ m.loadIfDirty();
+ row[AlbumSetLoader.INDEX_ID] = i;
+ row[AlbumSetLoader.INDEX_TITLE] = m.getName();
+ row[AlbumSetLoader.INDEX_COUNT] = m.getMediaItemCount();
+ MediaItem coverItem = m.getCoverMediaItem();
+ if (coverItem != null) {
+ row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs();
+ }
+ coverItems.add(coverItem);
+ cursor.addRow(row);
+ }
+ synchronized (mMediaSet) {
+ mCoverItems = coverItems;
+ }
+ return cursor;
+ }
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ BitmapJobDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+ drawable = new BitmapJobDrawable();
+ } else {
+ drawable = (BitmapJobDrawable) recycle;
+ }
+ int index = item.getInt(AlbumSetLoader.INDEX_ID);
+ drawable.setMediaItem(mCoverItems.get(index));
+ return drawable;
+ }
+
+ public static int getThumbnailSize() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public Uri uriForItem(Cursor item) {
+ int index = item.getInt(AlbumSetLoader.INDEX_ID);
+ MediaSet ms = mMediaSet.getSubMediaSet(index);
+ return ms == null ? null : ms.getContentUri();
+ }
+}
--- /dev/null
+/*
+ * 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.gallery3d.util;
+
+import android.app.Activity;
+import android.content.Context;
+
+public class UsageStatistics {
+ public static final boolean ENABLED = false;
+
+ public static final String COMPONENT_GALLERY = "Gallery";
+ public static final String COMPONENT_CAMERA = "Camera";
+ public static final String COMPONENT_EDITOR = "Editor";
+
+ public static final String TRANSITION_BACK_BUTTON = "BackButton";
+ public static final String TRANSITION_UP_BUTTON = "UpButton";
+ public static final String TRANSITION_PINCH_IN = "PinchIn";
+ public static final String TRANSITION_PINCH_OUT = "PinchOut";
+ public static final String TRANSITION_INTENT = "Intent";
+ public static final String TRANSITION_ITEM_TAP = "ItemTap";
+ public static final String TRANSITION_MENU_TAP = "MenuTap";
+ public static final String TRANSITION_BUTTON_TAP = "ButtonTap";
+ public static final String TRANSITION_SWIPE = "Swipe";
+
+ public static void initialize(Context context) {}
+ public static void showOptInDialogIfNeeded(Activity activity) {}
+ public static void setPendingTransitionCause(String cause) {}
+ public static void onContentViewChanged(String screenComponent, String screenName) {}
+ public static void onEvent(String category, String action, String label) {};
+ public static void onEvent(String category, String action, String label, long optional_value) {};
+}
private PhotoDatabase mDBHelper;
private static final String DB_NAME = "dummy.db";
+ private static final long PARENT_ID1 = 100;
+ private static final long PARENT_ID2 = 101;
@Override
protected void setUp() throws Exception {
SQLiteDatabase db = getWriteableDB();
db.beginTransaction();
try {
+ long accountId = 100;
// Test NOT NULL constraint on name
- assertFalse(PhotoDatabaseUtils
- .insertAlbum(db, null, null, Albums.VISIBILITY_PRIVATE, null));
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, null, Albums.VISIBILITY_PRIVATE,
+ accountId));
// test NOT NULL constraint on privacy
- assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello", null, null));
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello", null, accountId));
- // Normal insert
- assertTrue(PhotoDatabaseUtils.insertAlbum(db, null, "hello", Albums.VISIBILITY_PRIVATE,
- 100L));
-
- // Test server id uniqueness
- assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "world", Albums.VISIBILITY_PRIVATE,
- 100L));
-
- // Different server id allowed
- assertTrue(PhotoDatabaseUtils.insertAlbum(db, null, "world", Albums.VISIBILITY_PRIVATE,
- 101L));
-
- // Allow null server id
- assertTrue(PhotoDatabaseUtils.insertAlbum(db, null, "hello world",
+ // test NOT NULL constraint on account_id
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello",
Albums.VISIBILITY_PRIVATE, null));
- long albumId = PhotoDatabaseUtils.queryAlbumIdFromServerId(db, 100);
+ // Normal insert
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID1, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ long albumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID1);
// Assign a valid child
- assertTrue(PhotoDatabaseUtils.insertAlbum(db, albumId, "hello", Albums.VISIBILITY_PRIVATE,
- null));
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID2, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
- long otherAlbumId = PhotoDatabaseUtils.queryAlbumIdFromServerId(db, 101);
+ long otherAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID2);
assertNotSame(albumId, otherAlbumId);
// This is a valid child of another album.
assertTrue(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
- Albums.VISIBILITY_PRIVATE, null));
+ Albums.VISIBILITY_PRIVATE, accountId));
// This isn't allowed due to uniqueness constraint (parent_id/name)
assertFalse(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
- Albums.VISIBILITY_PRIVATE, null));
+ Albums.VISIBILITY_PRIVATE, accountId));
} finally {
db.endTransaction();
}
int height = 100;
long dateTaken = System.currentTimeMillis();
String mimeType = "test/test";
+ long accountId = 100;
// Test NOT NULL mime-type
- assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, width, height, dateTaken, null,
- null));
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, null,
+ accountId));
// Test NOT NULL width
- assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, null, height, dateTaken, null,
- mimeType));
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, height, dateTaken, null, mimeType,
+ accountId));
// Test NOT NULL height
- assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, width, null, dateTaken, null,
- mimeType));
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, null, dateTaken, null, mimeType,
+ accountId));
// Test NOT NULL dateTaken
- assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, width, height, null, null,
- mimeType));
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, null, null, mimeType,
+ accountId));
+
+ // Test NOT NULL accountId
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null,
+ mimeType, null));
// Test normal insert
- assertTrue(PhotoDatabaseUtils.insertPhoto(db, null, width, height, dateTaken, null,
- mimeType));
+ assertTrue(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, mimeType,
+ accountId));
} finally {
db.endTransaction();
}
db.beginTransaction();
try {
final String mimeType = "test/test";
- long photoServerId = 100;
- PhotoDatabaseUtils.insertPhoto(db, photoServerId, 100, 100, 100L, null, mimeType);
- long photoId = PhotoDatabaseUtils.queryPhotoIdFromServerId(db, photoServerId);
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, 100L, PARENT_ID1, mimeType, 100L);
+ long photoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, PARENT_ID1);
// Test NOT NULL PHOTO_ID constraint.
assertFalse(PhotoDatabaseUtils.insertMetadata(db, null, "foo", "bar"));
}
}
+ public void testAccountsConstraints() {
+ SQLiteDatabase db = getWriteableDB();
+ db.beginTransaction();
+ try {
+ assertFalse(PhotoDatabaseUtils.insertAccount(db, null));
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
private SQLiteDatabase getReadableDB() {
return mDBHelper.getReadableDatabase();
}
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import com.android.photos.data.PhotoProvider.Accounts;
import com.android.photos.data.PhotoProvider.Albums;
import com.android.photos.data.PhotoProvider.Metadata;
import com.android.photos.data.PhotoProvider.Photos;
public class PhotoDatabaseUtils {
public static String[] PROJECTION_ALBUMS = {
Albums._ID,
+ Albums.ACCOUNT_ID,
Albums.PARENT_ID,
Albums.VISIBILITY,
- Albums.NAME,
- Albums.SERVER_ID,
+ Albums.LOCATION_STRING,
+ Albums.TITLE,
+ Albums.SUMMARY,
+ Albums.DATE_PUBLISHED,
+ Albums.DATE_MODIFIED,
};
public static String[] PROJECTION_METADATA = {
public static String[] PROJECTION_PHOTOS = {
Photos._ID,
- Photos.SERVER_ID,
+ Photos.ACCOUNT_ID,
Photos.WIDTH,
Photos.HEIGHT,
Photos.DATE_TAKEN,
Photos.ALBUM_ID,
Photos.MIME_TYPE,
+ Photos.TITLE,
+ Photos.DATE_MODIFIED,
+ Photos.ROTATION,
};
- private static String SELECTION_ALBUM_SERVER_ID = Albums.SERVER_ID + " = ?";
- private static String SELECTION_PHOTO_SERVER_ID = Photos.SERVER_ID + " = ?";
+ public static String[] PROJECTION_ACCOUNTS = {
+ Accounts._ID,
+ Accounts.ACCOUNT_NAME,
+ };
+
+ private static String SELECTION_ALBUM_PARENT_ID = Albums.PARENT_ID + " = ?";
+ private static String SELECTION_PHOTO_ALBUM_ID = Photos.ALBUM_ID + " = ?";
- public static long queryAlbumIdFromServerId(SQLiteDatabase db, long serverId) {
- return queryId(db, Albums.TABLE, PROJECTION_ALBUMS, SELECTION_ALBUM_SERVER_ID, serverId);
+ public static long queryAlbumIdFromParentId(SQLiteDatabase db, long parentId) {
+ return queryId(db, Albums.TABLE, PROJECTION_ALBUMS, SELECTION_ALBUM_PARENT_ID, parentId);
}
- public static long queryPhotoIdFromServerId(SQLiteDatabase db, long serverId) {
- return queryId(db, Photos.TABLE, PROJECTION_PHOTOS, SELECTION_PHOTO_SERVER_ID, serverId);
+ public static long queryPhotoIdFromAlbumId(SQLiteDatabase db, long albumId) {
+ return queryId(db, Photos.TABLE, PROJECTION_PHOTOS, SELECTION_PHOTO_ALBUM_ID, albumId);
}
public static long queryId(SQLiteDatabase db, String table, String[] projection,
}
}
- public static boolean insertPhoto(SQLiteDatabase db, Long serverId, Integer width,
- Integer height, Long dateTaken, Long albumId, String mimeType) {
+ public static boolean insertPhoto(SQLiteDatabase db, Integer width, Integer height,
+ Long dateTaken, Long albumId, String mimeType, Long accountId) {
ContentValues values = new ContentValues();
- values.put(Photos.SERVER_ID, serverId);
values.put(Photos.WIDTH, width);
values.put(Photos.HEIGHT, height);
values.put(Photos.DATE_TAKEN, dateTaken);
values.put(Photos.ALBUM_ID, albumId);
values.put(Photos.MIME_TYPE, mimeType);
+ values.put(Photos.ACCOUNT_ID, accountId);
return db.insert(Photos.TABLE, null, values) != -1;
}
- public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String name,
- Integer privacy, Long serverId) {
+ public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String title,
+ Integer privacy, Long accountId) {
ContentValues values = new ContentValues();
values.put(Albums.PARENT_ID, parentId);
- values.put(Albums.NAME, name);
+ values.put(Albums.TITLE, title);
values.put(Albums.VISIBILITY, privacy);
- values.put(Albums.SERVER_ID, serverId);
+ values.put(Albums.ACCOUNT_ID, accountId);
return db.insert(Albums.TABLE, null, values) != -1;
}
values.put(Metadata.VALUE, value);
return db.insert(Metadata.TABLE, null, values) != -1;
}
+
+ public static boolean insertAccount(SQLiteDatabase db, String name) {
+ ContentValues values = new ContentValues();
+ values.put(Accounts.ACCOUNT_NAME, name);
+ return db.insert(Accounts.TABLE, null, values) != -1;
+ }
}
*/
package com.android.photos.data;
+import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
+import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
+import android.os.RemoteException;
import android.provider.BaseColumns;
import android.test.ProviderTestCase2;
import com.android.photos.data.PhotoProvider.Metadata;
import com.android.photos.data.PhotoProvider.Photos;
+import java.util.ArrayList;
+
public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
@SuppressWarnings("unused")
private static final String TAG = PhotoProviderTest.class.getSimpleName();
private static final String MIME_TYPE = "test/test";
- private static final String ALBUM_NAME = "My Album";
- private static final long ALBUM_SERVER_ID = 100;
- private static final long PHOTO_SERVER_ID = 50;
+ private static final String ALBUM_TITLE = "My Album";
+ private static final long ALBUM_PARENT_ID = 100;
private static final String META_KEY = "mykey";
private static final String META_VALUE = "myvalue";
SQLiteDatabase db = mDBHelper.getWritableDatabase();
db.beginTransaction();
try {
- PhotoDatabaseUtils.insertAlbum(db, null, ALBUM_NAME, Albums.VISIBILITY_PRIVATE,
- ALBUM_SERVER_ID);
- mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromServerId(db, ALBUM_SERVER_ID);
- PhotoDatabaseUtils.insertPhoto(db, PHOTO_SERVER_ID, 100, 100,
- System.currentTimeMillis(), mAlbumId, MIME_TYPE);
- mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromServerId(db, PHOTO_SERVER_ID);
+ PhotoDatabaseUtils.insertAlbum(db, ALBUM_PARENT_ID, ALBUM_TITLE,
+ Albums.VISIBILITY_PRIVATE, 100L);
+ mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, ALBUM_PARENT_ID);
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), mAlbumId,
+ MIME_TYPE, 100L);
+ mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, mAlbumId);
PhotoDatabaseUtils.insertMetadata(db, mPhotoId, META_KEY, META_VALUE);
String[] projection = {
BaseColumns._ID,
public void testInsert() {
ContentValues values = new ContentValues();
- values.put(Albums.NAME, "don't add me");
+ values.put(Albums.TITLE, "add me");
values.put(Albums.VISIBILITY, Albums.VISIBILITY_PRIVATE);
- assertNull(mResolver.insert(Albums.CONTENT_URI, values));
+ values.put(Albums.ACCOUNT_ID, 100L);
+ values.put(Albums.DATE_MODIFIED, 100L);
+ values.put(Albums.DATE_PUBLISHED, 100L);
+ values.put(Albums.LOCATION_STRING, "Home");
+ values.put(Albums.TITLE, "hello world");
+ values.putNull(Albums.PARENT_ID);
+ values.put(Albums.SUMMARY, "Nothing much to say about this");
+ Uri insertedUri = mResolver.insert(Albums.CONTENT_URI, values);
+ assertNotNull(insertedUri);
+ Cursor cursor = mResolver.query(insertedUri, PhotoDatabaseUtils.PROJECTION_ALBUMS, null,
+ null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.close();
}
public void testUpdate() {
ContentValues values = new ContentValues();
// Normal update -- use an album.
- values.put(Albums.NAME, "foo");
+ values.put(Albums.TITLE, "foo");
Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
assertEquals(1, mResolver.update(albumUri, values, null, null));
String[] projection = {
- Albums.NAME,
+ Albums.TITLE,
};
Cursor cursor = mResolver.query(albumUri, projection, null, null, null);
assertEquals(1, cursor.getCount());
// Update a row that doesn't exist.
Uri noAlbumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId + 1);
- values.put(Albums.NAME, "bar");
+ values.put(Albums.TITLE, "bar");
assertEquals(0, mResolver.update(noAlbumUri, values, null, null));
// Update a metadata value that exists.
mResolver.update(Metadata.CONTENT_URI, values, null, null);
assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
}
+
+ public void testBatchTransaction() throws RemoteException, OperationApplicationException {
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ ContentProviderOperation.Builder insert = ContentProviderOperation
+ .newInsert(Photos.CONTENT_URI);
+ insert.withValue(Photos.WIDTH, 200L);
+ insert.withValue(Photos.HEIGHT, 100L);
+ insert.withValue(Photos.DATE_TAKEN, System.currentTimeMillis());
+ insert.withValue(Photos.ALBUM_ID, 1000L);
+ insert.withValue(Photos.MIME_TYPE, "image/jpg");
+ insert.withValue(Photos.ACCOUNT_ID, 1L);
+ operations.add(insert.build());
+ ContentProviderOperation.Builder update = ContentProviderOperation.newUpdate(Photos.CONTENT_URI);
+ update.withValue(Photos.DATE_MODIFIED, System.currentTimeMillis());
+ String[] whereArgs = {
+ "100",
+ };
+ String where = Photos.WIDTH + " = ?";
+ update.withSelection(where, whereArgs);
+ operations.add(update.build());
+ ContentProviderOperation.Builder delete = ContentProviderOperation
+ .newDelete(Photos.CONTENT_URI);
+ delete.withSelection(where, whereArgs);
+ operations.add(delete.build());
+ mResolver.applyBatch(PhotoProvider.AUTHORITY, operations);
+ assertEquals(3, mNotifications.notificationCount());
+ SQLiteDatabase db = mDBHelper.getReadableDatabase();
+ long id = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, 1000L);
+ Uri uri = ContentUris.withAppendedId(Photos.CONTENT_URI, id);
+ assertTrue(mNotifications.isNotified(uri));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ }
+
}