OSDN Git Service

Merge "Implements double-tap to zoom" into gb-ub-photos-bryce
authornicolasroard <nicolasroard@google.com>
Fri, 8 Mar 2013 23:28:51 +0000 (23:28 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 8 Mar 2013 23:28:52 +0000 (23:28 +0000)
87 files changed:
AndroidManifest.xml
res/layout-land/camera_controls.xml
res/layout-port/camera_controls.xml
res/layout/album_set.xml [new file with mode: 0644]
res/layout/album_set_item.xml [new file with mode: 0644]
res/layout/camera_main.xml
res/layout/photo_set.xml
res/layout/photo_set_item.xml [new file with mode: 0644]
res/values-af/filtershow_strings.xml
res/values-am/filtershow_strings.xml
res/values-ar/filtershow_strings.xml
res/values-be/filtershow_strings.xml
res/values-bg/filtershow_strings.xml
res/values-ca/filtershow_strings.xml
res/values-cs/filtershow_strings.xml
res/values-da/filtershow_strings.xml
res/values-de/filtershow_strings.xml
res/values-el/filtershow_strings.xml
res/values-en-rGB/filtershow_strings.xml
res/values-es-rUS/filtershow_strings.xml
res/values-es/filtershow_strings.xml
res/values-et/filtershow_strings.xml
res/values-fa/filtershow_strings.xml
res/values-fi/filtershow_strings.xml
res/values-fr/filtershow_strings.xml
res/values-hi/filtershow_strings.xml
res/values-hr/filtershow_strings.xml
res/values-hu/filtershow_strings.xml
res/values-in/filtershow_strings.xml
res/values-it/filtershow_strings.xml
res/values-iw/filtershow_strings.xml
res/values-ja/filtershow_strings.xml
res/values-ko/filtershow_strings.xml
res/values-lt/filtershow_strings.xml
res/values-lv/filtershow_strings.xml
res/values-ms/filtershow_strings.xml
res/values-nb/filtershow_strings.xml
res/values-nl/filtershow_strings.xml
res/values-pl/filtershow_strings.xml
res/values-pt-rPT/filtershow_strings.xml
res/values-pt/filtershow_strings.xml
res/values-ro/filtershow_strings.xml
res/values-ru/filtershow_strings.xml
res/values-sk/filtershow_strings.xml
res/values-sl/filtershow_strings.xml
res/values-sr/filtershow_strings.xml
res/values-sv/filtershow_strings.xml
res/values-sw/filtershow_strings.xml
res/values-th/filtershow_strings.xml
res/values-tl/filtershow_strings.xml
res/values-tr/filtershow_strings.xml
res/values-uk/filtershow_strings.xml
res/values-vi/filtershow_strings.xml
res/values-zh-rCN/filtershow_strings.xml
res/values-zh-rTW/filtershow_strings.xml
res/values-zu/filtershow_strings.xml
res/values/dimens.xml
res/values/dimensions.xml
src/com/android/camera/CameraActivity.java
src/com/android/camera/PhotoModule.java
src/com/android/camera/ShutterButton.java
src/com/android/camera/ui/CameraSwitcher.java
src/com/android/camera/ui/RotatableLayout.java
src/com/android/gallery3d/app/AbstractGalleryActivity.java
src/com/android/gallery3d/app/GalleryAppImpl.java
src/com/android/gallery3d/app/StateManager.java
src/com/android/gallery3d/filtershow/FilterShowActivity.java
src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
src/com/android/gallery3d/filtershow/filters/grey.rs [new file with mode: 0644]
src/com/android/photos/AlbumSetFragment.java
src/com/android/photos/PhotoSetFragment.java
src/com/android/photos/data/AlbumSetLoader.java [new file with mode: 0644]
src/com/android/photos/data/MediaSetLoader.java [deleted file]
src/com/android/photos/data/NotificationWatcher.java
src/com/android/photos/data/PhotoDatabase.java
src/com/android/photos/data/PhotoProvider.java
src/com/android/photos/data/PhotoSetLoader.java
src/com/android/photos/data/SQLiteContentProvider.java [new file with mode: 0644]
src/com/android/photos/drawables/AutoThumbnailDrawable.java
src/com/android/photos/shims/BitmapJobDrawable.java [new file with mode: 0644]
src/com/android/photos/shims/LoaderCompatShim.java [new file with mode: 0644]
src/com/android/photos/shims/MediaItemsLoader.java [new file with mode: 0644]
src/com/android/photos/shims/MediaSetLoader.java [new file with mode: 0644]
src_pd/com/android/gallery3d/util/UsageStatistics.java [new file with mode: 0644]
tests/src/com/android/photos/data/PhotoDatabaseTest.java
tests/src/com/android/photos/data/PhotoDatabaseUtils.java
tests/src/com/android/photos/data/PhotoProviderTest.java

index abe76d2..b3c5f5c 100644 (file)
@@ -1,7 +1,7 @@
 <?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">
 
@@ -88,7 +88,8 @@
              </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"
index a909c51..f0f3ecc 100644 (file)
      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>
index caad868..aa15da1 100644 (file)
      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
diff --git a/res/layout/album_set.xml b/res/layout/album_set.xml
new file mode 100644 (file)
index 0000000..5ff1d23
--- /dev/null
@@ -0,0 +1,26 @@
+<?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
diff --git a/res/layout/album_set_item.xml b/res/layout/album_set_item.xml
new file mode 100644 (file)
index 0000000..bdecd5f
--- /dev/null
@@ -0,0 +1,42 @@
+<?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
index 657c06c..710e69d 100644 (file)
@@ -25,6 +25,8 @@
         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
index f6ff637..d929cad 100644 (file)
@@ -5,12 +5,18 @@
     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"
diff --git a/res/layout/photo_set_item.xml b/res/layout/photo_set_item.xml
new file mode 100644 (file)
index 0000000..b56184e
--- /dev/null
@@ -0,0 +1,7 @@
+<?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
index 93263e1..0797bfc 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index fc8ed5f..87a13e8 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 4259390..9801326 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 5c247d1..fd1caee 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 2cf26c4..1c907d4 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index ffa9d54..242464c 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 4df14c5..d8681f7 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 626e5e6..38f9698 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 571bd2b..555d53f 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index cd68de4..b22363f 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index c04751e..fcaf45c 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 24ac2aa..4efffad 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 90f14c3..ebd8c2c 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 02ab0b8..161ac90 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 817cfaa..db3dae4 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index fb944b4..92892a6 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 9f20d75..24685c0 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index b5e209d..6029cd1 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 8885ced..459a08e 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index e69d195..504eabc 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 4cf3937..f9a118b 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index ae19a56..6f8fff2 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index cc99514..e707495 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index e933bc8..65b53a1 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 1393a7d..902649f 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 2dd54ff..2f91fb6 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 56ddf69..155784c 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 95d0cdb..3f583b3 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 0e84ae7..a7b4396 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 420061d..302cc22 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 5a69884..880cf82 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 3df2b6c..389d63a 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 58aef86..14a7520 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 51fe75e..68b067c 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 222a047..cb18bed 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index b735763..b999871 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 85547ed..61bd61e 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 816545e..b6d66cb 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index babbc93..3644a5f 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 2d500d1..6f66386 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index b0b5e5b..2cdaf87 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 69596b2..4861dde 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 8b50f91..63740ec 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 8aaf125..c236088 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 08bfdb2..660172b 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index ab48612..1c060bc 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index c39c8de..f2a3679 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index dbc1f0f..22126bf 100644 (file)
@@ -34,7 +34,7 @@
     <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>
index 581c761..d2720d9 100644 (file)
@@ -88,7 +88,8 @@
     <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>
index dc9e8c3..ae50680 100644 (file)
@@ -19,7 +19,7 @@
     <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>
@@ -50,4 +50,8 @@
     <!--  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>
index eed3470..24c49ba 100644 (file)
@@ -61,7 +61,6 @@ public class CameraActivity extends ActivityBase
     private View mCameraControls;
     private View mControlsBackground;
     private View mPieMenuButton;
-    private View mSwitcherControl;
     private Drawable[] mDrawables;
     private int mCurrentModuleIndex;
     private MotionEvent mDown;
@@ -118,12 +117,13 @@ public class CameraActivity extends ActivityBase
     }
 
     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--;
@@ -222,11 +222,8 @@ public class CameraActivity extends ActivityBase
                 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) {
@@ -236,6 +233,18 @@ public class CameraActivity extends ActivityBase
         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
@@ -309,22 +318,22 @@ public class CameraActivity extends ActivityBase
     @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
@@ -465,9 +474,10 @@ public class CameraActivity extends ActivityBase
         }
         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);
         }
     }
 
index 664246a..4049aa5 100644 (file)
@@ -54,6 +54,7 @@ import android.view.SurfaceHolder;
 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;
@@ -206,6 +207,16 @@ public class PhotoModule
         }
     };
 
+    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];
@@ -461,6 +472,7 @@ public class PhotoModule
 
         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);
@@ -586,8 +598,8 @@ public class PhotoModule
         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) {
@@ -604,6 +616,21 @@ public class PhotoModule
         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) {
@@ -1618,6 +1645,9 @@ public class PhotoModule
         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();
index 41775a1..228fc51 100755 (executable)
@@ -130,10 +130,4 @@ public class ShutterButton extends ImageView {
         }
         return result;
     }
-
-    @Override
-    public void onConfigurationChanged(Configuration config) {
-        super.onConfigurationChanged(config);
-        RotatableLayout.rotate(this, config.orientation == Configuration.ORIENTATION_PORTRAIT);
-    }
 }
index 3fa4a01..ce4f850 100644 (file)
@@ -19,11 +19,13 @@ package com.android.camera.ui;
 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;
@@ -33,6 +35,7 @@ import android.view.ViewGroup;
 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;
 
@@ -120,6 +123,13 @@ public class CameraSwitcher extends RotateImageView
                 (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--) {
@@ -130,7 +140,7 @@ public class CameraSwitcher extends RotateImageView
             item.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    onCameraSelected(index);
+                    if (showsPopup()) onCameraSelected(index);
                 }
             });
             switch (mDrawIds[i]) {
@@ -163,10 +173,14 @@ public class CameraSwitcher extends RotateImageView
 
     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() {
@@ -224,15 +238,22 @@ public class CameraSwitcher extends RotateImageView
     }
 
     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;
@@ -254,7 +275,7 @@ public class CameraSwitcher extends RotateImageView
                 @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;
index 9c5ebd3..4edec5d 100644 (file)
 
 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
@@ -33,6 +37,8 @@ import android.widget.FrameLayout;
 
 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);
     }
@@ -46,11 +52,24 @@ public class RotatableLayout extends FrameLayout {
     }
 
     @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);
@@ -58,6 +77,13 @@ public class RotatableLayout extends FrameLayout {
         }
     }
 
+    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);
index d960942..bd7e654 100644 (file)
@@ -45,6 +45,7 @@ import com.android.gallery3d.ui.GLRoot;
 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")
@@ -75,6 +76,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext
         mPanoramaViewHelper = new PanoramaViewHelper(this);
         mPanoramaViewHelper.onCreate();
         doBindBatchService();
+        UsageStatistics.showOptInDialogIfNeeded(this);
     }
 
     @Override
index 561589b..5b4a872 100644 (file)
@@ -31,6 +31,7 @@ import com.android.gallery3d.picasasource.PicasaSource;
 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;
 
@@ -54,6 +55,7 @@ public class GalleryAppImpl extends Application implements GalleryApp {
         GalleryUtils.initialize(this);
         WidgetUtils.initialize(this);
         PicasaSource.initialize(this);
+        UsageStatistics.initialize(this);
 
         mStitchingProgressManager = LightCycleHelper.createStitchingManagerInstance(this);
         if (mStitchingProgressManager != null) {
index d77279f..b4b5d4b 100644 (file)
@@ -26,6 +26,7 @@ import android.view.MenuItem;
 
 import com.android.gallery3d.anim.StateTransitionAnimation;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.UsageStatistics;
 
 import java.util.Stack;
 
@@ -62,6 +63,9 @@ public class StateManager {
                     StateTransitionAnimation.Transition.Incoming);
             if (mIsResumed) top.onPause();
         }
+        UsageStatistics.onContentViewChanged(
+                UsageStatistics.COMPONENT_GALLERY,
+                klass.getSimpleName());
         state.initialize(mActivity, data);
 
         mStack.push(new StateEntry(data, state));
@@ -91,7 +95,8 @@ public class StateManager {
         } 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();
@@ -210,6 +215,10 @@ public class StateManager {
         state.onDestroy();
 
         if (top != null && mIsResumed) top.resume();
+        if (top != null) {
+            UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+                    top.getClass().getSimpleName());
+        }
     }
 
     public void switchState(ActivityState oldState,
@@ -241,6 +250,8 @@ public class StateManager {
         mStack.push(new StateEntry(data, state));
         state.onCreate(data, null);
         if (mIsResumed) state.resume();
+        UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+                klass.getSimpleName());
     }
 
     public void destroy() {
@@ -255,6 +266,7 @@ public class StateManager {
     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 =
@@ -273,6 +285,11 @@ public class StateManager {
             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());
         }
     }
 
index 4b59565..33fdef5 100644 (file)
@@ -700,6 +700,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
     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);
index a3467ed..4373c95 100644 (file)
@@ -20,6 +20,7 @@ import android.app.Activity;
 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";
@@ -118,4 +119,23 @@ public abstract class ImageFilterRS extends ImageFilter {
         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;
+
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/grey.rs b/src/com/android/gallery3d/filtershow/filters/grey.rs
new file mode 100644 (file)
index 0000000..e018803
--- /dev/null
@@ -0,0 +1,22 @@
+  /*
+ * 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;
+}
index 2d348c2..0d4fcc0 100644 (file)
 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);
+        }
+    }
 }
index 0e9efa4..18587fc 100644 (file)
@@ -22,38 +22,54 @@ import android.content.Context;
 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();
@@ -63,18 +79,39 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
     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();
     }
 
@@ -82,46 +119,37 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
     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
diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java
new file mode 100644 (file)
index 0000000..b2b5204
--- /dev/null
@@ -0,0 +1,51 @@
+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
diff --git a/src/com/android/photos/data/MediaSetLoader.java b/src/com/android/photos/data/MediaSetLoader.java
deleted file mode 100644 (file)
index 4afb7d9..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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;
-    }
-
-}
index 8cf0e3c..9041c23 100644 (file)
@@ -19,8 +19,7 @@ import android.net.Uri;
 
 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
@@ -28,11 +27,13 @@ import java.util.Set;
  * 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) {
@@ -43,7 +44,12 @@ public class NotificationWatcher implements ChangeNotification {
         return mUris.size();
     }
 
+    public boolean syncToNetwork() {
+        return mSyncToNetwork;
+    }
+
     public void reset() {
         mUris.clear();
+        mSyncToNetwork = false;
     }
 }
index 35de185..a87f00b 100644 (file)
@@ -19,10 +19,14 @@ import android.content.Context;
 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.
@@ -36,23 +40,32 @@ public class PhotoDatabase extends SQLiteOpenHelper {
 
     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 = {
@@ -64,11 +77,17 @@ public class PhotoDatabase extends SQLiteOpenHelper {
         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) {
@@ -79,7 +98,23 @@ public class PhotoDatabase extends SQLiteOpenHelper {
     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;
@@ -107,4 +142,25 @@ public class PhotoDatabase extends SQLiteOpenHelper {
                 "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);
+            }
+        }
+    }
 }
index 27afb58..cecfe5e 100644 (file)
  */
 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;
 
 /**
@@ -44,7 +46,7 @@ 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();
 
@@ -56,32 +58,41 @@ public class PhotoProvider extends ContentProvider {
     // 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
@@ -94,42 +105,47 @@ public class PhotoProvider extends ContentProvider {
          * 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;
@@ -141,35 +157,53 @@ public class PhotoProvider extends ContentProvider {
      * 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";
 
         /**
@@ -179,9 +213,10 @@ public class PhotoProvider extends ContentProvider {
         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
@@ -190,16 +225,20 @@ public class PhotoProvider extends ContentProvider {
         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";
     };
 
@@ -233,7 +272,6 @@ public class PhotoProvider extends ContentProvider {
     };
 
     protected ChangeNotification mNotifier = null;
-    private SQLiteOpenHelper mOpenHelper;
     protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
     protected static final int MATCH_PHOTO = 1;
@@ -243,6 +281,7 @@ public class PhotoProvider extends ContentProvider {
     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);
@@ -256,29 +295,20 @@ public class PhotoProvider extends ContentProvider {
         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;
     }
 
@@ -294,20 +324,19 @@ public class PhotoProvider extends ContentProvider {
     }
 
     @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
@@ -323,31 +352,26 @@ public class PhotoProvider extends ContentProvider {
         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;
     }
 
@@ -416,31 +440,21 @@ public class PhotoProvider extends ContentProvider {
         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;
     }
@@ -450,14 +464,18 @@ public class PhotoProvider extends ContentProvider {
         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);
         }
     }
 
@@ -471,44 +489,55 @@ public class PhotoProvider extends ContentProvider {
         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.");
         }
     }
 }
index 8c511a5..78662cd 100644 (file)
@@ -19,15 +19,20 @@ package com.android.photos.data;
 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,
@@ -67,4 +72,22 @@ public class PhotoSetLoader extends CursorLoader {
         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;
+    }
 }
diff --git a/src/com/android/photos/data/SQLiteContentProvider.java b/src/com/android/photos/data/SQLiteContentProvider.java
new file mode 100644 (file)
index 0000000..ecd868b
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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
index 09b3441..b51b670 100644 (file)
@@ -167,7 +167,9 @@ public abstract class AutoThumbnailDrawable<T> extends Drawable {
     }
 
     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) {
diff --git a/src/com/android/photos/shims/BitmapJobDrawable.java b/src/com/android/photos/shims/BitmapJobDrawable.java
new file mode 100644 (file)
index 0000000..9d3c7e9
--- /dev/null
@@ -0,0 +1,164 @@
+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);
+        }
+    }
+
+}
diff --git a/src/com/android/photos/shims/LoaderCompatShim.java b/src/com/android/photos/shims/LoaderCompatShim.java
new file mode 100644 (file)
index 0000000..9da4436
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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);
+}
diff --git a/src/com/android/photos/shims/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java
new file mode 100644 (file)
index 0000000..9c270a5
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java
new file mode 100644 (file)
index 0000000..96c7485
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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();
+    }
+}
diff --git a/src_pd/com/android/gallery3d/util/UsageStatistics.java b/src_pd/com/android/gallery3d/util/UsageStatistics.java
new file mode 100644 (file)
index 0000000..cf27ac5
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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) {};
+}
index d8c5e42..70edee2 100644 (file)
@@ -31,6 +31,8 @@ public class PhotoDatabaseTest extends InstrumentationTestCase {
 
     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 {
@@ -68,45 +70,38 @@ public class PhotoDatabaseTest extends InstrumentationTestCase {
         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();
         }
@@ -120,26 +115,31 @@ public class PhotoDatabaseTest extends InstrumentationTestCase {
             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();
         }
@@ -150,9 +150,8 @@ public class PhotoDatabaseTest extends InstrumentationTestCase {
         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"));
@@ -167,6 +166,18 @@ public class PhotoDatabaseTest extends InstrumentationTestCase {
         }
     }
 
+    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();
     }
index 73a6c78..97db8bf 100644 (file)
@@ -19,6 +19,7 @@ import android.content.ContentValues;
 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;
@@ -28,10 +29,14 @@ import junit.framework.AssertionFailedError;
 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 = {
@@ -42,23 +47,31 @@ public class PhotoDatabaseUtils {
 
     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,
@@ -79,25 +92,25 @@ public class PhotoDatabaseUtils {
         }
     }
 
-    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;
     }
 
@@ -108,4 +121,10 @@ public class PhotoDatabaseUtils {
         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;
+    }
 }
index 525abec..39abff4 100644 (file)
  */
 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;
 
@@ -29,14 +32,15 @@ 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;
+
 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";
 
@@ -70,12 +74,12 @@ public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
         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,
@@ -189,19 +193,32 @@ public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
 
     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());
@@ -211,7 +228,7 @@ public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
 
         // 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.
@@ -305,4 +322,38 @@ public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
         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));
+    }
+
 }