summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/layout/album_set_item.xml2
-rw-r--r--res/layout/photo_set.xml10
-rw-r--r--res/layout/photo_set_item.xml7
-rw-r--r--res/values-be/filtershow_strings.xml3
-rw-r--r--res/values-et/filtershow_strings.xml3
-rw-r--r--res/values-fr/filtershow_strings.xml3
-rw-r--r--res/values-ko/filtershow_strings.xml3
-rw-r--r--res/values-lt/filtershow_strings.xml3
-rw-r--r--res/values-lv/filtershow_strings.xml3
-rw-r--r--res/values-pl/filtershow_strings.xml3
-rw-r--r--res/values-pt/filtershow_strings.xml3
-rw-r--r--res/values-ro/filtershow_strings.xml3
-rw-r--r--res/values-ru/filtershow_strings.xml3
-rw-r--r--res/values-sk/filtershow_strings.xml3
-rw-r--r--res/values-sv/filtershow_strings.xml3
-rw-r--r--res/values-sw/filtershow_strings.xml3
-rw-r--r--res/values-tr/filtershow_strings.xml3
-rw-r--r--res/values-zh-rCN/filtershow_strings.xml3
-rw-r--r--src/com/android/photos/AlbumSetFragment.java70
-rw-r--r--src/com/android/photos/PhotoSetFragment.java66
-rw-r--r--src/com/android/photos/data/AlbumSetLoader.java22
-rw-r--r--src/com/android/photos/data/MediaSetLoader.java89
-rw-r--r--src/com/android/photos/data/NotificationWatcher.java14
-rw-r--r--src/com/android/photos/data/PhotoDatabase.java5
-rw-r--r--src/com/android/photos/data/PhotoProvider.java184
-rw-r--r--src/com/android/photos/data/PhotoSetLoader.java22
-rw-r--r--src/com/android/photos/data/SQLiteContentProvider.java264
-rw-r--r--src/com/android/photos/drawables/DrawableFactory.java24
-rw-r--r--src/com/android/photos/shims/BitmapJobDrawable.java158
-rw-r--r--src/com/android/photos/shims/MediaItemsLoader.java148
-rw-r--r--src/com/android/photos/shims/MediaSetLoader.java143
-rw-r--r--tests/src/com/android/photos/data/PhotoDatabaseUtils.java5
-rw-r--r--tests/src/com/android/photos/data/PhotoProviderTest.java66
33 files changed, 1053 insertions, 291 deletions
diff --git a/res/layout/album_set_item.xml b/res/layout/album_set_item.xml
index 46084e938..bdecd5fd1 100644
--- a/res/layout/album_set_item.xml
+++ b/res/layout/album_set_item.xml
@@ -12,6 +12,8 @@
android:layout_alignParentTop="true"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
+ android:ellipsize="end"
+ android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
diff --git a/res/layout/photo_set.xml b/res/layout/photo_set.xml
index f6ff637d6..d929cadfb 100644
--- a/res/layout/photo_set.xml
+++ b/res/layout/photo_set.xml
@@ -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
index 000000000..b56184e59
--- /dev/null
+++ b/res/layout/photo_set_item.xml
@@ -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
diff --git a/res/values-be/filtershow_strings.xml b/res/values-be/filtershow_strings.xml
index 3a83c23b9..fd1caee53 100644
--- a/res/values-be/filtershow_strings.xml
+++ b/res/values-be/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Скінуць"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-et/filtershow_strings.xml b/res/values-et/filtershow_strings.xml
index 01225b86c..161ac9048 100644
--- a/res/values-et/filtershow_strings.xml
+++ b/res/values-et/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Lähtesta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-fr/filtershow_strings.xml b/res/values-fr/filtershow_strings.xml
index edeafa061..24685c02c 100644
--- a/res/values-fr/filtershow_strings.xml
+++ b/res/values-fr/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Réinitialiser"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-ko/filtershow_strings.xml b/res/values-ko/filtershow_strings.xml
index 7eaa641ed..902649f48 100644
--- a/res/values-ko/filtershow_strings.xml
+++ b/res/values-ko/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"초기화"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-lt/filtershow_strings.xml b/res/values-lt/filtershow_strings.xml
index 54407b16e..2f91fb629 100644
--- a/res/values-lt/filtershow_strings.xml
+++ b/res/values-lt/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Nust. iš naujo"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-lv/filtershow_strings.xml b/res/values-lv/filtershow_strings.xml
index 7fbfb8c42..155784c49 100644
--- a/res/values-lv/filtershow_strings.xml
+++ b/res/values-lv/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Atiestatīt"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-pl/filtershow_strings.xml b/res/values-pl/filtershow_strings.xml
index 834677741..880cf824b 100644
--- a/res/values-pl/filtershow_strings.xml
+++ b/res/values-pl/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Resetuj"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-pt/filtershow_strings.xml b/res/values-pt/filtershow_strings.xml
index d5ba68138..14a7520a2 100644
--- a/res/values-pt/filtershow_strings.xml
+++ b/res/values-pt/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Restaurar"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-ro/filtershow_strings.xml b/res/values-ro/filtershow_strings.xml
index f59b3d011..68b067cc9 100644
--- a/res/values-ro/filtershow_strings.xml
+++ b/res/values-ro/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Resetaţi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-ru/filtershow_strings.xml b/res/values-ru/filtershow_strings.xml
index ad9f545c4..cb18bedaa 100644
--- a/res/values-ru/filtershow_strings.xml
+++ b/res/values-ru/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Сброс"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-sk/filtershow_strings.xml b/res/values-sk/filtershow_strings.xml
index 7a5e093ac..b999871b4 100644
--- a/res/values-sk/filtershow_strings.xml
+++ b/res/values-sk/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Obnoviť"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-sv/filtershow_strings.xml b/res/values-sv/filtershow_strings.xml
index 109db1320..3644a5ff5 100644
--- a/res/values-sv/filtershow_strings.xml
+++ b/res/values-sv/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Återställ"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-sw/filtershow_strings.xml b/res/values-sw/filtershow_strings.xml
index 93db6df69..6f66386b1 100644
--- a/res/values-sw/filtershow_strings.xml
+++ b/res/values-sw/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Weka upya"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-tr/filtershow_strings.xml b/res/values-tr/filtershow_strings.xml
index 2566ff450..63740ec27 100644
--- a/res/values-tr/filtershow_strings.xml
+++ b/res/values-tr/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Sıfırla"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/res/values-zh-rCN/filtershow_strings.xml b/res/values-zh-rCN/filtershow_strings.xml
index 26b14ed0b..1c060bc57 100644
--- a/res/values-zh-rCN/filtershow_strings.xml
+++ b/res/values-zh-rCN/filtershow_strings.xml
@@ -34,8 +34,7 @@
<string name="reset" msgid="9013181350779592937">"重置"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <!-- no translation found for imageState (8632586742752891968) -->
- <skip />
+ <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>
diff --git a/src/com/android/photos/AlbumSetFragment.java b/src/com/android/photos/AlbumSetFragment.java
index 6a9520a5e..3c51bbac3 100644
--- a/src/com/android/photos/AlbumSetFragment.java
+++ b/src/com/android/photos/AlbumSetFragment.java
@@ -17,14 +17,15 @@
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.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
@@ -37,15 +38,20 @@ import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.photos.data.AlbumSetLoader;
-import com.android.photos.drawables.DataUriThumbnailDrawable;
+import com.android.photos.drawables.DrawableFactory;
+import com.android.photos.shims.MediaSetLoader;
import java.util.Date;
-public class AlbumSetFragment extends Fragment implements OnItemClickListener {
+public class AlbumSetFragment extends Fragment implements OnItemClickListener,
+ LoaderCallbacks<Cursor> {
+
private GridView mAlbumSetView;
private View mEmptyView;
- private CursorAdapter mAdapter;
+ private AlbumSetCursorAdapter mAdapter;
+
+ private static final int LOADER_ALBUMSET = 1;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -57,11 +63,37 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener {
mAdapter = new AlbumSetCursorAdapter(getActivity());
mAlbumSetView.setAdapter(mAdapter);
mAlbumSetView.setOnItemClickListener(this);
- mAdapter.swapCursor(AlbumSetLoader.MOCK);
+ 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);
@@ -71,6 +103,11 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener {
private static class AlbumSetCursorAdapter extends CursorAdapter {
+ private DrawableFactory<Cursor> mDrawableFactory;
+
+ public void setDrawableFactory(DrawableFactory<Cursor> factory) {
+ mDrawableFactory = factory;
+ }
private Date mDate = new Date(); // Used for converting timestamps for display
public AlbumSetCursorAdapter(Context context) {
@@ -85,8 +122,13 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener {
TextView dateTextView = (TextView) v.findViewById(
R.id.album_set_item_date);
- mDate.setTime(cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP));
- dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate));
+ 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);
@@ -97,17 +139,19 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener {
uploadProgressBar.setVisibility(View.INVISIBLE);
}
- // TODO show the thumbnail
+ 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) {
- View v = LayoutInflater.from(context).inflate(
+ return LayoutInflater.from(context).inflate(
R.layout.album_set_item, parent, false);
- ImageView thumbImageView = (ImageView) v.findViewById(
- R.id.album_set_item_image);
- thumbImageView.setImageResource(android.R.color.darker_gray);
- return v;
}
}
}
diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java
index 0e9efa4b1..1de8de5a7 100644
--- a/src/com/android/photos/PhotoSetFragment.java
+++ b/src/com/android/photos/PhotoSetFragment.java
@@ -19,22 +19,22 @@ package com.android.photos;
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
-import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
-import android.net.Uri;
+import android.graphics.drawable.Drawable;
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.CursorAdapter;
+import android.widget.GridView;
import android.widget.ImageView;
import com.android.gallery3d.R;
import com.android.photos.data.PhotoSetLoader;
-import com.android.photos.drawables.DataUriThumbnailDrawable;
-import com.android.photos.views.GalleryThumbnailView;
+import com.android.photos.drawables.DrawableFactory;
+import com.android.photos.shims.MediaItemsLoader;
import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
@@ -42,7 +42,7 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
private static final int LOADER_PHOTOSET = 1;
- private GalleryThumbnailView mPhotoSetView;
+ private GridView mPhotoSetView;
private View mEmptyView;
private ThumbnailAdapter mAdapter;
@@ -50,7 +50,9 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
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());
mEmptyView = root.findViewById(android.R.id.empty);
mEmptyView.setVisibility(View.GONE);
mAdapter = new ThumbnailAdapter(getActivity());
@@ -68,7 +70,10 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
@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);
+ return loader;
}
@Override
@@ -82,46 +87,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 DrawableFactory<Cursor> mDrawableFactory;
public ThumbnailAdapter(Context context) {
super(context, null, false);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void setDrawableFactory(DrawableFactory<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
index f5fc3b732..b2b5204e6 100644
--- a/src/com/android/photos/data/AlbumSetLoader.java
+++ b/src/com/android/photos/data/AlbumSetLoader.java
@@ -13,20 +13,20 @@ public class AlbumSetLoader {
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) {
- String[] rows = {
- "_id",
- "title",
- "timestamp",
- "thumb_uri",
- "thumb_width",
- "thumb_height",
- "count_pending_upload",
- "_count"
- };
- MatrixCursor c = new MatrixCursor(rows, count);
+ MatrixCursor c = new MatrixCursor(PROJECTION, count);
for (int i = 0; i < count; i++) {
c.addRow(createRandomRow());
}
diff --git a/src/com/android/photos/data/MediaSetLoader.java b/src/com/android/photos/data/MediaSetLoader.java
deleted file mode 100644
index 4afb7d922..000000000
--- a/src/com/android/photos/data/MediaSetLoader.java
+++ /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;
- }
-
-}
diff --git a/src/com/android/photos/data/NotificationWatcher.java b/src/com/android/photos/data/NotificationWatcher.java
index 8cf0e3c8f..9041c236f 100644
--- a/src/com/android/photos/data/NotificationWatcher.java
+++ b/src/com/android/photos/data/NotificationWatcher.java
@@ -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;
}
}
diff --git a/src/com/android/photos/data/PhotoDatabase.java b/src/com/android/photos/data/PhotoDatabase.java
index 8585edc04..a87f00bfa 100644
--- a/src/com/android/photos/data/PhotoDatabase.java
+++ b/src/com/android/photos/data/PhotoDatabase.java
@@ -59,14 +59,13 @@ public class PhotoDatabase extends SQLiteOpenHelper {
{ 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.LOCATION_STRING, "TEXT" },
- { Albums.TITLE, "TEXT" },
+ { Albums.TITLE, "TEXT NOT NULL" },
{ Albums.SUMMARY, "TEXT" },
{ Albums.DATE_PUBLISHED, "INTEGER" },
{ Albums.DATE_MODIFIED, "INTEGER" },
- createUniqueConstraint(Albums.PARENT_ID, Albums.NAME),
+ createUniqueConstraint(Albums.PARENT_ID, Albums.TITLE),
};
private static final String[][] CREATE_METADATA = {
diff --git a/src/com/android/photos/data/PhotoProvider.java b/src/com/android/photos/data/PhotoProvider.java
index 52ebd6eee..cecfe5ea4 100644
--- a/src/com/android/photos/data/PhotoProvider.java
+++ b/src/com/android/photos/data/PhotoProvider.java
@@ -15,8 +15,10 @@
*/
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;
@@ -28,7 +30,6 @@ import android.net.Uri;
import android.os.CancellationSignal;
import android.provider.BaseColumns;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -45,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();
@@ -57,7 +58,7 @@ 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);
}
/**
@@ -130,8 +131,6 @@ public class PhotoProvider extends ContentProvider {
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 name of the album. String value. */
- public static final String NAME = "name";
/**
* Column name for the visibility level of the album. Can be any of the
* VISIBILITY_* values.
@@ -214,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
@@ -224,8 +224,18 @@ 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. */
- public static final String PHOTO_ID = "photo_id";
+ /**
+ * Content URI for retrieving the album cover art. The album ID must be
+ * appended to the URI.
+ */
+ public static final Uri ALBUM_COVER_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI,
+ Albums.TABLE);
+
+ /**
+ * 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. */
@@ -262,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;
@@ -272,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);
@@ -285,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;
}
@@ -323,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
@@ -352,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;
}
@@ -445,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;
}
@@ -479,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);
}
}
@@ -500,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.");
}
}
}
diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java
index 8c511a525..21da90694 100644
--- a/src/com/android/photos/data/PhotoSetLoader.java
+++ b/src/com/android/photos/data/PhotoSetLoader.java
@@ -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.drawables.DrawableFactory;
+
+public class PhotoSetLoader extends CursorLoader implements DrawableFactory<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,17 @@ 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;
+ }
}
diff --git a/src/com/android/photos/data/SQLiteContentProvider.java b/src/com/android/photos/data/SQLiteContentProvider.java
new file mode 100644
index 000000000..ecd868b52
--- /dev/null
+++ b/src/com/android/photos/data/SQLiteContentProvider.java
@@ -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
diff --git a/src/com/android/photos/drawables/DrawableFactory.java b/src/com/android/photos/drawables/DrawableFactory.java
new file mode 100644
index 000000000..ad046c820
--- /dev/null
+++ b/src/com/android/photos/drawables/DrawableFactory.java
@@ -0,0 +1,24 @@
+/*
+ * 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.drawables;
+
+import android.graphics.drawable.Drawable;
+
+
+public interface DrawableFactory<T> {
+ Drawable drawableForItem(T item, Drawable recycle);
+}
diff --git a/src/com/android/photos/shims/BitmapJobDrawable.java b/src/com/android/photos/shims/BitmapJobDrawable.java
new file mode 100644
index 000000000..299becb07
--- /dev/null
+++ b/src/com/android/photos/shims/BitmapJobDrawable.java
@@ -0,0 +1,158 @@
+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;
+import com.android.photos.drawables.AutoThumbnailDrawable;
+
+
+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();
+
+ public BitmapJobDrawable() {
+ }
+
+ public void setMediaItem(MediaItem item) {
+ if (mLoader != null) {
+ mLoader.cancelLoad();
+ }
+ mItem = item;
+ if (mBitmap != null) {
+ GalleryBitmapPool.getInstance().put(mBitmap);
+ mBitmap = null;
+ }
+ // TODO: Figure out why ThumbnailLoader doesn't like to be re-used
+ mLoader = new ThumbnailLoader(this);
+ mLoader.startLoad();
+ 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.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/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java
new file mode 100644
index 000000000..886b3c3a1
--- /dev/null
+++ b/src/com/android/photos/shims/MediaItemsLoader.java
@@ -0,0 +1,148 @@
+/*
+ * 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.provider.MediaStore.Files.FileColumns;
+
+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;
+import com.android.photos.drawables.DrawableFactory;
+
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear
+ * like a PhotoSetLoader
+ */
+public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements DrawableFactory<Cursor> {
+
+ 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();
+ }
+ };
+ private ArrayList<MediaItem> mMediaItems = new ArrayList<MediaItem>();
+
+ 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];
+ 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);
+ mMediaItems.add(item);
+ }
+ });
+ 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);
+ }
+
+}
diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java
new file mode 100644
index 000000000..7a6fcb865
--- /dev/null
+++ b/src/com/android/photos/shims/MediaSetLoader.java
@@ -0,0 +1,143 @@
+/*
+ * 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.provider.MediaStore.Files.FileColumns;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaDetails;
+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.AlbumSetLoader;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.drawables.DrawableFactory;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+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 DrawableFactory<Cursor>{
+
+ 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();
+ }
+ };
+
+ private ArrayList<MediaItem> mCoverItems = new ArrayList<MediaItem>();
+
+ 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();
+ 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();
+ row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs();
+ mCoverItems.add(coverItem);
+ cursor.addRow(row);
+ }
+ 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);
+ }
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseUtils.java b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
index 1840eb1be..97db8bf7d 100644
--- a/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
+++ b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
@@ -31,7 +31,6 @@ public class PhotoDatabaseUtils {
Albums._ID,
Albums.ACCOUNT_ID,
Albums.PARENT_ID,
- Albums.NAME,
Albums.VISIBILITY,
Albums.LOCATION_STRING,
Albums.TITLE,
@@ -105,11 +104,11 @@ public class PhotoDatabaseUtils {
return db.insert(Photos.TABLE, null, values) != -1;
}
- public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String name,
+ 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.ACCOUNT_ID, accountId);
return db.insert(Albums.TABLE, null, values) != -1;
diff --git a/tests/src/com/android/photos/data/PhotoProviderTest.java b/tests/src/com/android/photos/data/PhotoProviderTest.java
index 2e644a3d6..39abff441 100644
--- a/tests/src/com/android/photos/data/PhotoProviderTest.java
+++ b/tests/src/com/android/photos/data/PhotoProviderTest.java
@@ -15,13 +15,16 @@
*/
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,12 +32,14 @@ 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 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";
@@ -69,7 +74,7 @@ public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
SQLiteDatabase db = mDBHelper.getWritableDatabase();
db.beginTransaction();
try {
- PhotoDatabaseUtils.insertAlbum(db, ALBUM_PARENT_ID, ALBUM_NAME,
+ 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,
@@ -188,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());
@@ -210,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.
@@ -304,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));
+ }
+
}