summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2012-09-06 16:46:57 -0400
committerChris Wren <cwren@android.com>2012-09-07 17:41:42 -0400
commitd85f53c69dead1f1f6c0290b8104422143bc5166 (patch)
tree2a8d5cec293a0b55937a0a8296ed995f2af39bb8
parent83fee9012b6d5c5940de5b96fe8d98653ba14c0d (diff)
downloadandroid_packages_screensavers_PhotoTable-d85f53c69dead1f1f6c0290b8104422143bc5166.tar.gz
android_packages_screensavers_PhotoTable-d85f53c69dead1f1f6c0290b8104422143bc5166.tar.bz2
android_packages_screensavers_PhotoTable-d85f53c69dead1f1f6c0290b8104422143bc5166.zip
Add ability to select the albums to display.
Change-Id: I80ff33c4c880c445b79735d6483bc9337a89e392
-rw-r--r--AndroidManifest.xml25
-rw-r--r--res/layout/album.xml19
-rw-r--r--res/values/config.xml4
-rw-r--r--res/values/ids.xml1
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/dreams/phototable/AlbumDataAdapter.java117
-rw-r--r--src/com/android/dreams/phototable/AlbumSettings.java44
-rw-r--r--src/com/android/dreams/phototable/FlipperDream.java2
-rw-r--r--src/com/android/dreams/phototable/FlipperDreamSettings.java52
-rw-r--r--src/com/android/dreams/phototable/LocalSource.java85
-rw-r--r--src/com/android/dreams/phototable/PhotoCarousel.java16
-rw-r--r--src/com/android/dreams/phototable/PhotoSource.java34
-rw-r--r--src/com/android/dreams/phototable/PhotoSourcePlexor.java45
-rw-r--r--src/com/android/dreams/phototable/PhotoTable.java428
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDream.java40
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDreamSettings.java52
-rw-r--r--src/com/android/dreams/phototable/PhotoTouchListener.java4
-rw-r--r--src/com/android/dreams/phototable/PicasaSource.java180
-rw-r--r--src/com/android/dreams/phototable/StockSource.java25
-rw-r--r--src/com/android/dreams/phototable/Table.java448
20 files changed, 1097 insertions, 528 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4511728..2e6c39c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -10,26 +10,47 @@
android:icon="@mipmap/icon"
android:largeHeap="true">
android:hardwareAccelerated="true"
- <service android:name="PhotoTable"
+ <service android:name="PhotoTableDream"
android:exported="true"
android:icon="@mipmap/icon"
android:label="@string/table_screensaver_name">
+ <meta-data
+ android:name="android.service.dreams.config_activity"
+ android:value="com.android.dreams.phototable/.PhotoTableDreamSettings"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DREAM" />
</intent-filter>
</service>
+ <activity android:name="PhotoTableDreamSettings"
+ android:exported="true"
+ android:label="@string/table_screensaver_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<service android:name="FlipperDream"
android:exported="true"
android:icon="@mipmap/flip"
android:label="@string/flipper_screensaver_name">
- android:icon="@mipmap/flip"
+ <meta-data
+ android:name="android.service.dreams.config_activity"
+ android:value="com.android.dreams.phototable/.FlipperDreamSettings"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DREAM" />
</intent-filter>
</service>
+ <activity android:name="FlipperDreamSettings"
+ android:exported="true"
+ android:label="@string/flipper_screensaver_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/res/layout/album.xml b/res/layout/album.xml
new file mode 100644
index 0000000..890d96c
--- /dev/null
+++ b/res/layout/album.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal" >
+ <CheckBox android:id="@+id/enabled" />
+</LinearLayout >
diff --git a/res/values/config.xml b/res/values/config.xml
index 8039f28..1305cf7 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -52,5 +52,9 @@
<!-- Parts per million damping coefficient of the table. -->
<integer name="table_damping">950000</integer>
+
+ <!-- Maximum number of albums to pull for "Photos from Posts". -->
+ <integer name="max_post_albums">100</integer>
+
</resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 3f65a0c..4159edc 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -17,4 +17,5 @@
<resources>
<item type="id" name="photo_height" />
<item type="id" name="photo_width" />
+ <item type="id" name="data_payload" />
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 897f5b2..83de506 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,4 +17,8 @@
<string name="app_name">Photo Screensavers</string>
<string name="table_screensaver_name">Photo Table</string>
<string name="flipper_screensaver_name">Photo Flipper</string>
+ <string name="posts_album_name">Photos from Posts</string>
+ <string name="unknown_album_name">Unnamed Album</string>
+ <string name="stock_photo_album_name">Stock Photos</string>
+ <string name="stock_photo_thumbnail_url">https://lh4.googleusercontent.com/-Wd4RWlOXkSo/STt6r8uEJBI/AAAAAAAABJk/2An3QS-aD5c/s256/044_002.jpg</string>
</resources>
diff --git a/src/com/android/dreams/phototable/AlbumDataAdapter.java b/src/com/android/dreams/phototable/AlbumDataAdapter.java
new file mode 100644
index 0000000..3d68627
--- /dev/null
+++ b/src/com/android/dreams/phototable/AlbumDataAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dreams.phototable;
+
+import android.content.SharedPreferences;
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Settings panel for photo flipping dream.
+ */
+public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> {
+ private static final String TAG = "AlbumDataAdapter";
+
+ public static final String ALBUM_SET = "Enabled Album Set";
+
+ private final SharedPreferences mSettings;
+ private final LayoutInflater mInflater;
+ private final int mLayout;
+ private final ItemClickListener mListener;
+
+ private Set<String> mEnabledAlbums;
+
+ public AlbumDataAdapter(Context context, SharedPreferences settings,
+ int resource, List<PhotoSource.AlbumData> objects) {
+ super(context, resource, objects);
+ mSettings = settings;
+ mLayout = resource;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mListener = new ItemClickListener();
+ mEnabledAlbums = AlbumSettings.getEnabledAlbums(mSettings);
+ }
+
+ @Override
+ public View getView (int position, View convertView, ViewGroup parent) {
+ View item = convertView;
+ if (item == null) {
+ item = mInflater.inflate(mLayout, parent, false);
+ } else {
+ }
+ PhotoSource.AlbumData data = getItem(position);
+
+ View vCheckBox = item.findViewById(R.id.enabled);
+ if (vCheckBox != null && vCheckBox instanceof CheckBox) {
+ CheckBox checkBox = (CheckBox) vCheckBox;
+ checkBox.setOnCheckedChangeListener(null);
+ checkBox.setChecked(mEnabledAlbums.contains(data.id));
+ checkBox.setText(data.toString());
+ checkBox.setTag(R.id.data_payload, data);
+ checkBox.setOnCheckedChangeListener(mListener);
+ }
+
+ return item;
+ }
+
+ public static class RecencyComparator implements Comparator<PhotoSource.AlbumData> {
+ @Override
+ public int compare(PhotoSource.AlbumData a, PhotoSource.AlbumData b) {
+ if (a.updated == b.updated) {
+ return a.title.compareTo(b.title);
+ } else {
+ return (int) Math.signum(b.updated - a.updated);
+ }
+ }
+ }
+
+ public static class AlphabeticalComparator implements Comparator<PhotoSource.AlbumData> {
+ @Override
+ public int compare(PhotoSource.AlbumData a, PhotoSource.AlbumData b) {
+ return a.title.compareTo(b.title);
+ }
+ }
+
+ private class ItemClickListener implements CompoundButton.OnCheckedChangeListener {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ PhotoSource.AlbumData data =
+ (PhotoSource.AlbumData) buttonView.getTag(R.id.data_payload);
+
+ if (isChecked) {
+ mEnabledAlbums.add(data.id);
+ } else {
+ mEnabledAlbums.remove(data.id);
+ }
+
+ AlbumSettings.setEnabledAlbums(mSettings , mEnabledAlbums);
+
+ Log.i("adaptor", data.title + " is " + (isChecked ? "" : "not") + " enabled");
+ }
+ }
+}
diff --git a/src/com/android/dreams/phototable/AlbumSettings.java b/src/com/android/dreams/phototable/AlbumSettings.java
new file mode 100644
index 0000000..62a0162
--- /dev/null
+++ b/src/com/android/dreams/phototable/AlbumSettings.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dreams.phototable;
+
+import android.content.SharedPreferences;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Common utilities for album settings.
+ */
+public class AlbumSettings {
+ public static final String ALBUM_SET = "Enabled Album Set";
+
+ public static Set<String> getEnabledAlbums(SharedPreferences settings) {
+ Set<String> enabled = settings.getStringSet(ALBUM_SET, null);
+ if (enabled == null) {
+ enabled= new HashSet<String>();
+ enabled.add(StockSource.ALBUM_ID);
+ setEnabledAlbums(settings, enabled);
+ }
+ return enabled;
+ }
+
+ public static void setEnabledAlbums(SharedPreferences settings, Set<String> value) {
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putStringSet(ALBUM_SET, value);
+ editor.commit();
+ }
+}
diff --git a/src/com/android/dreams/phototable/FlipperDream.java b/src/com/android/dreams/phototable/FlipperDream.java
index f7b1cfe..f2e20b9 100644
--- a/src/com/android/dreams/phototable/FlipperDream.java
+++ b/src/com/android/dreams/phototable/FlipperDream.java
@@ -21,7 +21,7 @@ import android.service.dreams.Dream;
* Example interactive screen saver: single photo with flipping.
*/
public class FlipperDream extends Dream {
- private static final String TAG = "FlipperDream";
+ public static final String TAG = "FlipperDream";
@Override
public void onStart() {
diff --git a/src/com/android/dreams/phototable/FlipperDreamSettings.java b/src/com/android/dreams/phototable/FlipperDreamSettings.java
new file mode 100644
index 0000000..0ae20df
--- /dev/null
+++ b/src/com/android/dreams/phototable/FlipperDreamSettings.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dreams.phototable;
+
+import android.content.SharedPreferences;
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+
+import java.util.LinkedList;
+
+/**
+ * Settings panel for photo flipping dream.
+ */
+public class FlipperDreamSettings extends ListActivity {
+ private static final String TAG = "FlipperDreamSettings";
+ public static final String PREFS_NAME = FlipperDream.TAG;
+
+ private PhotoSourcePlexor mPhotoSource;
+ private ArrayAdapter<PhotoSource.AlbumData> mAdapter;
+ private SharedPreferences mSettings;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+
+ //setContentView(R.layout.custom_list_activity_view);
+
+ mSettings = getSharedPreferences(PREFS_NAME, 0);
+
+ mPhotoSource = new PhotoSourcePlexor(this, mSettings);
+ mAdapter = new AlbumDataAdapter(this,
+ mSettings,
+ R.layout.album,
+ new LinkedList<PhotoSource.AlbumData>(mPhotoSource.findAlbums()));
+ mAdapter.sort(new AlbumDataAdapter.RecencyComparator());
+ setListAdapter(mAdapter);
+ }
+}
diff --git a/src/com/android/dreams/phototable/LocalSource.java b/src/com/android/dreams/phototable/LocalSource.java
index 8dbc079..ec23b6a 100644
--- a/src/com/android/dreams/phototable/LocalSource.java
+++ b/src/com/android/dreams/phototable/LocalSource.java
@@ -16,12 +16,14 @@
package com.android.dreams.phototable;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.provider.MediaStore;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedList;
/**
@@ -32,30 +34,94 @@ public class LocalSource extends PhotoSource {
private int mNextPosition;
- public static final int TYPE = 2;
-
- public LocalSource(Context context) {
- super(context);
+ public LocalSource(Context context, SharedPreferences settings) {
+ super(context, settings);
mSourceName = TAG;
mNextPosition = -1;
fillQueue();
}
@Override
+ public Collection<AlbumData> findAlbums() {
+ log(TAG, "finding albums");
+ HashMap<String, AlbumData> foundAlbums = new HashMap<String, AlbumData>();
+
+ String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.BUCKET_ID,
+ MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN};
+ // This is a horrible hack that closes the where clause and injects a grouping clause.
+ Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ projection, null, null, null);
+ if (cursor != null) {
+ cursor.moveToFirst();
+
+ int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
+ int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
+ int nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
+ int updatedIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN);
+
+ if (bucketIndex < 0) {
+ log(TAG, "can't find the ID column!");
+ } else {
+ while (!cursor.isAfterLast()) {
+ String id = TAG + ":" + cursor.getString(bucketIndex);
+ AlbumData data = foundAlbums.get(id);
+ if (foundAlbums.get(id) == null) {
+ data = new AlbumData();
+ data.id = id;
+
+ if (dataIndex >= 0) {
+ data.thumbnailUrl = cursor.getString(dataIndex);
+ }
+
+ if (nameIndex >= 0) {
+ data.title = cursor.getString(nameIndex);
+ } else {
+ data.title =
+ mResources.getString(R.string.unknown_album_name, "Unknown");
+ }
+
+ log(TAG, data.title + " found");
+ foundAlbums.put(id, data);
+ }
+ if (updatedIndex >= 0) {
+ long updated = cursor.getLong(updatedIndex);
+ data.updated = (data.updated == 0 ?
+ updated :
+ Math.min(data.updated, updated));
+ }
+ cursor.moveToNext();
+ }
+ }
+ cursor.close();
+
+ }
+ log(TAG, "found " + foundAlbums.size() + " items.");
+ return foundAlbums.values();
+ }
+
+ @Override
protected Collection<ImageData> findImages(int howMany) {
log(TAG, "finding images");
LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
- String[] selectionArgs = {}; // settings go here
String selection = "";
- for (String arg : selectionArgs) {
- if (selection.length() > 0) {
- selection += " OR ";
+ for (String id : AlbumSettings.getEnabledAlbums(mSettings)) {
+ if (id.startsWith(TAG)) {
+ String[] parts = id.split(":");
+ if (parts.length > 1) {
+ if (selection.length() > 0) {
+ selection += " OR ";
+ }
+ selection += MediaStore.Images.Media.BUCKET_ID + " = '" + parts[1] + "'";
+ }
}
- selection += MediaStore.Images.Media.BUCKET_ID + " = '" + arg + "'";
}
+ if (selection.isEmpty()) {
+ return foundImages;
+ }
+
Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, selection, null, null);
if (cursor != null) {
@@ -77,7 +143,6 @@ public class LocalSource extends PhotoSource {
} else {
while (foundImages.size() < howMany && !cursor.isAfterLast()) {
ImageData data = new ImageData();
- data.type = TYPE;
data.url = cursor.getString(dataIndex);
data.orientation = cursor.getInt(orientationIndex);
foundImages.offer(data);
diff --git a/src/com/android/dreams/phototable/PhotoCarousel.java b/src/com/android/dreams/phototable/PhotoCarousel.java
index 838af18..99804d5 100644
--- a/src/com/android/dreams/phototable/PhotoCarousel.java
+++ b/src/com/android/dreams/phototable/PhotoCarousel.java
@@ -64,7 +64,8 @@ public class PhotoCarousel extends FrameLayout {
mFlipDuration = resources.getInteger(R.integer.flip_duration);
mOptions = new BitmapFactory.Options();
mOptions.inTempStorage = new byte[32768];
- mPhotoSource = new PhotoSourcePlexor(context);
+ mPhotoSource = new PhotoSourcePlexor(getContext(),
+ getContext().getSharedPreferences(FlipperDreamSettings.PREFS_NAME, 0));
mBitmapStore = new HashMap<View, Bitmap>();
mPanel = new View[2];
@@ -89,11 +90,9 @@ public class PhotoCarousel extends FrameLayout {
}
private class PhotoLoadTask extends AsyncTask<Void, Void, Bitmap> {
- private int mTries;
private ImageView mDestination;
public PhotoLoadTask(View destination) {
- mTries = 0;
mDestination = (ImageView) destination;
}
@@ -114,9 +113,14 @@ public class PhotoCarousel extends FrameLayout {
old.recycle();
}
PhotoCarousel.this.requestLayout();
- } else if (mTries < 3) {
- mTries++;
- this.execute();
+ } else {
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ new PhotoLoadTask(mDestination)
+ .execute();
+ }
+ }, 100);
}
}
};
diff --git a/src/com/android/dreams/phototable/PhotoSource.java b/src/com/android/dreams/phototable/PhotoSource.java
index 2851c6c..32c7739 100644
--- a/src/com/android/dreams/phototable/PhotoSource.java
+++ b/src/com/android/dreams/phototable/PhotoSource.java
@@ -17,6 +17,7 @@ package com.android.dreams.phototable;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
@@ -46,25 +47,43 @@ public abstract class PhotoSource {
// that we can mark and reset the input stream to avoid duplicate network i/o
private static final int BUFFER_SIZE = 128 * 1024;
- public static class ImageData {
+ public class ImageData {
public String id;
public String url;
public int orientation;
- public int type;
+
+ InputStream getStream() {
+ return PhotoSource.this.getStream(this);
+ }
+ }
+
+ public static class AlbumData {
+ public String id;
+ public String title;
+ public String thumbnailUrl;
+ public long updated;
+
+ @Override
+ public String toString() {
+ return title;
+ }
}
- private final Context mContext;
private final LinkedList<ImageData> mImageQueue;
private final int mMaxQueueSize;
+ protected final Context mContext;
protected final Resources mResources;
- protected ContentResolver mResolver;
- protected String mSourceName;
protected final Random mRNG;
+ protected final SharedPreferences mSettings;
+ protected final ContentResolver mResolver;
+
+ protected String mSourceName;
- public PhotoSource(Context context) {
+ public PhotoSource(Context context, SharedPreferences settings) {
mSourceName = TAG;
mContext = context;
+ mSettings = settings;
mResolver = mContext.getContentResolver();
mResources = context.getResources();
mImageQueue = new LinkedList<ImageData>();
@@ -91,7 +110,7 @@ public abstract class PhotoSource {
ImageData data = mImageQueue.poll();
InputStream is = null;
try {
- is = getStream(data);
+ is = data.getStream();
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(BUFFER_SIZE);
@@ -180,4 +199,5 @@ public abstract class PhotoSource {
protected abstract InputStream getStream(ImageData data);
protected abstract Collection<ImageData> findImages(int howMany);
+ public abstract Collection<AlbumData> findAlbums();
}
diff --git a/src/com/android/dreams/phototable/PhotoSourcePlexor.java b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
index 4c4550e..7cf207e 100644
--- a/src/com/android/dreams/phototable/PhotoSourcePlexor.java
+++ b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
@@ -16,6 +16,7 @@
package com.android.dreams.phototable;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
@@ -34,13 +35,33 @@ public class PhotoSourcePlexor extends PhotoSource {
private final PhotoSource mPicasaSource;
private final PhotoSource mLocalSource;
private final PhotoSource mStockSource;
+ private SharedPreferences mSettings;
- public PhotoSourcePlexor(Context context) {
- super(context);
+ public PhotoSourcePlexor(Context context, SharedPreferences settings) {
+ super(context, settings);
mSourceName = TAG;
- mPicasaSource = new PicasaSource(context);
- mLocalSource = new LocalSource(context);
- mStockSource = new StockSource(context);
+ mPicasaSource = new PicasaSource(context, settings);
+ mLocalSource = new LocalSource(context, settings);
+ mStockSource = new StockSource(context, settings);
+ }
+
+ @Override
+ public Collection<AlbumData> findAlbums() {
+ log(TAG, "finding albums");
+ LinkedList<AlbumData> foundAlbums = new LinkedList<AlbumData>();
+
+ foundAlbums.addAll(mPicasaSource.findAlbums());
+ log(TAG, "found " + foundAlbums.size() + " network albums");
+
+ foundAlbums.addAll(mLocalSource.findAlbums());
+ log(TAG, "found " + foundAlbums.size() + " user albums");
+
+ if (foundAlbums.isEmpty()) {
+ foundAlbums.addAll(mStockSource.findAlbums());
+ }
+ log(TAG, "found " + foundAlbums.size() + " albums");
+
+ return foundAlbums;
}
@Override
@@ -64,18 +85,6 @@ public class PhotoSourcePlexor extends PhotoSource {
@Override
protected InputStream getStream(ImageData data) {
- switch (data.type) {
- case PicasaSource.TYPE:
- return mPicasaSource.getStream(data);
-
- case LocalSource.TYPE:
- return mLocalSource.getStream(data);
-
- case StockSource.TYPE:
- return mStockSource.getStream(data);
-
- default:
- return null;
- }
+ return data.getStream();
}
}
diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java
index 49c4b92..7e6faea 100644
--- a/src/com/android/dreams/phototable/PhotoTable.java
+++ b/src/com/android/dreams/phototable/PhotoTable.java
@@ -16,25 +16,431 @@
package com.android.dreams.phototable;
import android.service.dreams.Dream;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PointF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+
+import java.util.LinkedList;
+import java.util.Random;
/**
- * Example interactive screen saver: flick photos onto a table.
+ * A surface where photos sit.
*/
-public class PhotoTable extends Dream {
+public class PhotoTable extends FrameLayout {
private static final String TAG = "PhotoTable";
- private Table mTable;
+ private static final boolean DEBUG = false;
+
+ class Launcher implements Runnable {
+ private final PhotoTable mTable;
+ public Launcher(PhotoTable table) {
+ mTable = table;
+ }
+
+ @Override
+ public void run() {
+ mTable.scheduleNext(mDropPeriod);
+ mTable.launch();
+ }
+ }
+
+ private static final long MAX_SELECTION_TIME = 10000L;
+ private static Random sRNG = new Random();
+
+ private final Launcher mLauncher;
+ private final LinkedList<View> mOnTable;
+ private final Dream mDream;
+ private final int mDropPeriod;
+ private final int mFastDropPeriod;
+ private final int mNowDropDelay;
+ private final float mImageRatio;
+ private final float mTableRatio;
+ private final float mImageRotationLimit;
+ private final boolean mTapToExit;
+ private final int mTableCapacity;
+ private final int mInset;
+ private final PhotoSourcePlexor mPhotoSource;
+ private final Resources mResources;
+ private PhotoLaunchTask mPhotoLaunchTask;
+ private boolean mStarted;
+ private boolean mIsLandscape;
+ private BitmapFactory.Options mOptions;
+ private int mLongSide;
+ private int mShortSide;
+ private int mWidth;
+ private int mHeight;
+ private View mSelected;
+ private long mSelectedTime;
+
+ public PhotoTable(Dream dream, AttributeSet as) {
+ super(dream, as);
+ mDream = dream;
+ mResources = getResources();
+ setBackground(mResources.getDrawable(R.drawable.table));
+ mInset = mResources.getDimensionPixelSize(R.dimen.photo_inset);
+ mDropPeriod = mResources.getInteger(R.integer.drop_period);
+ mFastDropPeriod = mResources.getInteger(R.integer.fast_drop);
+ mNowDropDelay = mResources.getInteger(R.integer.now_drop);
+ mImageRatio = mResources.getInteger(R.integer.image_ratio) / 1000000f;
+ mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f;
+ mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation);
+ mTableCapacity = mResources.getInteger(R.integer.table_capacity);
+ mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit);
+ mOnTable = new LinkedList<View>();
+ mOptions = new BitmapFactory.Options();
+ mOptions.inTempStorage = new byte[32768];
+ mPhotoSource = new PhotoSourcePlexor(getContext(),
+ getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0));
+ mLauncher = new Launcher(this);
+ mStarted = false;
+ }
+
+ public boolean hasSelection() {
+ return mSelected != null;
+ }
+
+ public View getSelected() {
+ return mSelected;
+ }
+
+ public void clearSelection() {
+ mSelected = null;
+ }
+
+ public void setSelection(View selected) {
+ assert(selected != null);
+ if (mSelected != null) {
+ dropOnTable(mSelected);
+ }
+ mSelected = selected;
+ mSelectedTime = System.currentTimeMillis();
+ bringChildToFront(selected);
+ pickUp(selected);
+ }
+
+ static float lerp(float a, float b, float f) {
+ return (b-a)*f + a;
+ }
+
+ static float randfrange(float a, float b) {
+ return lerp(a, b, sRNG.nextFloat());
+ }
+
+ static PointF randFromCurve(float t, PointF[] v) {
+ PointF p = new PointF();
+ if (v.length == 4 && t >= 0f && t <= 1f) {
+ float a = (float) Math.pow(1f-t, 3f);
+ float b = (float) Math.pow(1f-t, 2f) * t;
+ float c = (1f-t) * (float) Math.pow(t, 2f);
+ float d = (float) Math.pow(t, 3f);
+
+ p.x = a * v[0].x + 3 * b * v[1].x + 3 * c * v[2].x + d * v[3].x;
+ p.y = a * v[0].y + 3 * b * v[1].y + 3 * c * v[2].y + d * v[3].y;
+ }
+ return p;
+ }
+
+ private static PointF randInCenter(float i, float j, int width, int height) {
+ log("randInCenter (" + i + ", " + j + ", " + width + ", " + height + ")");
+ PointF p = new PointF();
+ p.x = 0.5f * width + 0.15f * width * i;
+ p.y = 0.5f * height + 0.15f * height * j;
+ log("randInCenter returning " + p.x + "," + p.y);
+ return p;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ if (hasSelection()) {
+ dropOnTable(getSelected());
+ clearSelection();
+ } else {
+ if (mTapToExit) {
+ mDream.finish();
+ }
+ }
+ return true;
+ }
+ return false;
+ }
@Override
- public void onStart() {
- super.onStart();
- setInteractive(true);
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ log("onLayout (" + left + ", " + top + ", " + right + ", " + bottom + ")");
+
+ mHeight = bottom - top;
+ mWidth = right - left;
+
+ mLongSide = (int) (mImageRatio * Math.max(mWidth, mHeight));
+ mShortSide = (int) (mImageRatio * Math.min(mWidth, mHeight));
+
+ boolean isLandscape = mWidth > mHeight;
+ if (mIsLandscape != isLandscape) {
+ for (View photo: mOnTable) {
+ if (photo == getSelected()) {
+ pickUp(photo);
+ } else {
+ dropOnTable(photo);
+ }
+ }
+ mIsLandscape = isLandscape;
+ }
+ start();
}
@Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mTable = new Table(this, null);
- setContentView(mTable);
- lightsOut();
+ public boolean isOpaque() {
+ return true;
+ }
+
+ private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
+ @Override
+ public View doInBackground(Void... unused) {
+ log("load a new photo");
+ final PhotoTable table = PhotoTable.this;
+
+ LayoutInflater inflater = (LayoutInflater) table.getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View photo = inflater.inflate(R.layout.photo, null);
+ ImageView image = (ImageView) photo;
+ Drawable[] layers = new Drawable[2];
+ Bitmap decodedPhoto = table.mPhotoSource.next(table.mOptions,
+ table.mLongSide, table.mShortSide);
+ int photoWidth = table.mOptions.outWidth;
+ int photoHeight = table.mOptions.outHeight;
+ if (table.mOptions.outWidth <= 0 || table.mOptions.outHeight <= 0) {
+ photo = null;
+ } else {
+ layers[0] = new BitmapDrawable(table.mResources, decodedPhoto);
+ layers[1] = table.mResources.getDrawable(R.drawable.frame);
+ LayerDrawable layerList = new LayerDrawable(layers);
+ layerList.setLayerInset(0, table.mInset, table.mInset,
+ table.mInset, table.mInset);
+ image.setImageDrawable(layerList);
+
+ photo.setTag(R.id.photo_width, new Integer(photoWidth));
+ photo.setTag(R.id.photo_height, new Integer(photoHeight));
+
+ photo.setOnTouchListener(new PhotoTouchListener(table.getContext(),
+ table));
+ }
+
+ return photo;
+ }
+
+ @Override
+ public void onPostExecute(View photo) {
+ if (photo != null) {
+ final PhotoTable table = PhotoTable.this;
+
+ table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ if (table.hasSelection()) {
+ table.bringChildToFront(table.getSelected());
+ }
+ int width = ((Integer) photo.getTag(R.id.photo_width)).intValue();
+ int height = ((Integer) photo.getTag(R.id.photo_height)).intValue();
+
+ log("drop it");
+ table.throwOnTable(photo);
+
+ if(table.mOnTable.size() < table.mTableCapacity) {
+ table.scheduleNext(table.mFastDropPeriod);
+ }
+ }
+ }
+ };
+
+ public void launch() {
+ log("launching");
+ setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+ if (hasSelection() &&
+ (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) {
+ dropOnTable(getSelected());
+ clearSelection();
+ } else {
+ log("inflate it");
+ if (mPhotoLaunchTask == null ||
+ mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
+ mPhotoLaunchTask = new PhotoLaunchTask();
+ mPhotoLaunchTask.execute();
+ }
+ }
+ }
+ public void fadeAway(final View photo, final boolean replace) {
+ // fade out of view
+ mOnTable.remove(photo);
+ photo.animate().cancel();
+ photo.animate()
+ .withLayer()
+ .alpha(0f)
+ .setDuration(1000)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ removeView(photo);
+ recycle(photo);
+ if (replace) {
+ scheduleNext(mNowDropDelay);
+ }
+ }
+ });
+ }
+
+ public void moveToBackOfQueue(View photo) {
+ // make this photo the last to be removed.
+ bringChildToFront(photo);
+ invalidate();
+ mOnTable.remove(photo);
+ mOnTable.offer(photo);
+ }
+
+ private void throwOnTable(final View photo) {
+ mOnTable.offer(photo);
+ log("start offscreen");
+ int width = ((Integer) photo.getTag(R.id.photo_width));
+ int height = ((Integer) photo.getTag(R.id.photo_height));
+ photo.setRotation(-100.0f);
+ photo.setX(-mLongSide);
+ photo.setY(-mLongSide);
+ dropOnTable(photo);
+ }
+
+ public void dropOnTable(final View photo) {
+ float angle = randfrange(-mImageRotationLimit, mImageRotationLimit);
+ PointF p = randInCenter((float) sRNG.nextGaussian(), (float) sRNG.nextGaussian(),
+ mWidth, mHeight);
+ float x = p.x;
+ float y = p.y;
+
+ log("drop it at " + x + ", " + y);
+
+ float x0 = photo.getX();
+ float y0 = photo.getY();
+ float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
+ float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
+
+ x -= mTableRatio * mLongSide / 2f;
+ y -= mTableRatio * mLongSide / 2f;
+ log("fixed offset is " + x + ", " + y);
+
+ float dx = x - x0;
+ float dy = y - y0;
+
+ float dist = (float) (Math.sqrt(dx * dx + dy * dy));
+ int duration = (int) (1000f * dist / 400f);
+ duration = Math.max(duration, 1000);
+
+ log("animate it");
+ // toss onto table
+ photo.animate()
+ .withLayer()
+ .scaleX(mTableRatio / mImageRatio)
+ .scaleY(mTableRatio / mImageRatio)
+ .rotation(angle)
+ .x(x)
+ .y(y)
+ .setDuration(duration)
+ .setInterpolator(new DecelerateInterpolator(3f))
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ while (mOnTable.size() > mTableCapacity) {
+ fadeAway(mOnTable.poll(), false);
+ }
+ }
+ });
+ }
+
+ /** wrap all orientations to the interval [-180, 180). */
+ private float wrapAngle(float angle) {
+ float result = angle + 180;
+ result = ((result % 360) + 360) % 360; // catch negative numbers
+ result -= 180;
+ return result;
+ }
+
+ private void pickUp(final View photo) {
+ float photoWidth = photo.getWidth();
+ float photoHeight = photo.getHeight();
+
+ float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
+
+ log("target it");
+ float x = (getWidth() - photoWidth) / 2f;
+ float y = (getHeight() - photoHeight) / 2f;
+
+ float x0 = photo.getX();
+ float y0 = photo.getY();
+ float dx = x - x0;
+ float dy = y - y0;
+
+ float dist = (float) (Math.sqrt(dx * dx + dy * dy));
+ int duration = (int) (1000f * dist / 1000f);
+ duration = Math.max(duration, 500);
+
+ photo.setRotation(wrapAngle(photo.getRotation()));
+
+ log("animate it");
+ // toss onto table
+ photo.animate()
+ .withLayer()
+ .rotation(0f)
+ .scaleX(scale)
+ .scaleY(scale)
+ .x(x)
+ .y(y)
+ .setDuration(duration)
+ .setInterpolator(new DecelerateInterpolator(2f))
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ log("endtimes: " + photo.getX());
+ }
+ });
+ }
+
+ private void recycle(View photo) {
+ ImageView image = (ImageView) photo;
+ LayerDrawable layers = (LayerDrawable) image.getDrawable();
+ BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0);
+ bitmap.getBitmap().recycle();
+ }
+
+ public void start() {
+ if (!mStarted) {
+ log("kick it");
+ mStarted = true;
+ scheduleNext(mDropPeriod);
+ launch();
+ }
+ }
+
+ public void scheduleNext(int delay) {
+ removeCallbacks(mLauncher);
+ postDelayed(mLauncher, delay);
+ }
+
+ private static void log(String message) {
+ if (DEBUG) {
+ Log.i(TAG, message);
+ }
}
}
diff --git a/src/com/android/dreams/phototable/PhotoTableDream.java b/src/com/android/dreams/phototable/PhotoTableDream.java
new file mode 100644
index 0000000..47c672d
--- /dev/null
+++ b/src/com/android/dreams/phototable/PhotoTableDream.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dreams.phototable;
+
+import android.service.dreams.Dream;
+
+/**
+ * Example interactive screen saver: flick photos onto a table.
+ */
+public class PhotoTableDream extends Dream {
+ public static final String TAG = "PhotoTableDream";
+ private PhotoTable mTable;
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ setInteractive(true);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTable = new PhotoTable(this, null);
+ setContentView(mTable);
+ lightsOut();
+ }
+}
diff --git a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
new file mode 100644
index 0000000..e7ba945
--- /dev/null
+++ b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dreams.phototable;
+
+import android.content.SharedPreferences;
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+
+import java.util.LinkedList;
+
+/**
+ * Settings panel for photo flipping dream.
+ */
+public class PhotoTableDreamSettings extends ListActivity {
+ private static final String TAG = "PhotoTableDreamSettings";
+ public static final String PREFS_NAME = PhotoTableDream.TAG;
+
+ private PhotoSourcePlexor mPhotoSource;
+ private ArrayAdapter<PhotoSource.AlbumData> mAdapter;
+ private SharedPreferences mSettings;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+
+ //setContentView(R.layout.custom_list_activity_view);
+
+ mSettings = getSharedPreferences(PREFS_NAME, 0);
+
+ mPhotoSource = new PhotoSourcePlexor(this, mSettings);
+ mAdapter = new AlbumDataAdapter(this,
+ mSettings,
+ R.layout.album,
+ new LinkedList<PhotoSource.AlbumData>(mPhotoSource.findAlbums()));
+ mAdapter.sort(new AlbumDataAdapter.RecencyComparator());
+ setListAdapter(mAdapter);
+ }
+}
diff --git a/src/com/android/dreams/phototable/PhotoTouchListener.java b/src/com/android/dreams/phototable/PhotoTouchListener.java
index 439991a..2cfcbd4 100644
--- a/src/com/android/dreams/phototable/PhotoTouchListener.java
+++ b/src/com/android/dreams/phototable/PhotoTouchListener.java
@@ -34,7 +34,7 @@ public class PhotoTouchListener implements View.OnTouchListener {
private static final int MAX_POINTER_COUNT = 10;
private final int mTouchSlop;
private final int mTapTimeout;
- private final Table mTable;
+ private final PhotoTable mTable;
private final float mBeta;
private final float mTableRatio;
private final boolean mEnableFling;
@@ -56,7 +56,7 @@ public class PhotoTouchListener implements View.OnTouchListener {
private float[] pts = new float[MAX_POINTER_COUNT];
private float[] tmp = new float[MAX_POINTER_COUNT];
- public PhotoTouchListener(Context context, Table table) {
+ public PhotoTouchListener(Context context, PhotoTable table) {
mTable = table;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
diff --git a/src/com/android/dreams/phototable/PicasaSource.java b/src/com/android/dreams/phototable/PicasaSource.java
index 061b66b..4d1c677 100644
--- a/src/com/android/dreams/phototable/PicasaSource.java
+++ b/src/com/android/dreams/phototable/PicasaSource.java
@@ -16,6 +16,7 @@
package com.android.dreams.phototable;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
@@ -23,6 +24,7 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedList;
/**
@@ -31,23 +33,39 @@ import java.util.LinkedList;
public class PicasaSource extends PhotoSource {
private static final String TAG = "PhotoTable.PicasaSource";
+ private static final String PICASA_AUTHORITY =
+ "com.google.android.gallery3d.GooglePhotoProvider";
+
+ private static final String PICASA_PHOTO_PATH = "photos";
+ private static final String PICASA_ALBUM_PATH = "albums";
+
private static final String PICASA_ID = "_id";
private static final String PICASA_URL = "content_url";
private static final String PICASA_ROTATION = "rotation";
private static final String PICASA_ALBUM_ID = "album_id";
+ private static final String PICASA_TITLE = "title";
+ private static final String PICASA_THUMB = "thumbnail_url";
+ private static final String PICASA_ALBUM_TYPE = "album_type";
+ private static final String PICASA_ALBUM_UPDATED = "date_updated";
private static final String PICASA_URL_KEY = "content_url";
private static final String PICASA_TYPE_KEY = "type";
- private static final String PICASA_TYPE_THUMB_VALUE = "full";
+ private static final String PICASA_TYPE_FULL_VALUE = "full";
+ private static final String PICASA_TYPE_SCREEN_VALUE = "screennail";
+ private static final String PICASA_TYPE_THUMB_VALUE = "thumbnail";
+ private static final String PICASA_TYPE_IMAGE_VALUE = "image";
+ private static final String PICASA_BUZZ_TYPE = "Buzz";
- private int mNextPosition;
+ private final int mMaxPostAblums;
- public static final int TYPE = 3;
+ private int mNextPosition;
- public PicasaSource(Context context) {
- super(context);
+ public PicasaSource(Context context, SharedPreferences settings) {
+ super(context, settings);
mSourceName = TAG;
mNextPosition = -1;
+ mMaxPostAblums = mResources.getInteger(R.integer.max_post_albums);
+ log(TAG, "mSettings: " + mSettings);
fillQueue();
}
@@ -56,20 +74,45 @@ public class PicasaSource extends PhotoSource {
log(TAG, "finding images");
LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
- String[] selectionArgs = {}; // settings go here
- String selection = "";
- for (String arg : selectionArgs) {
- if (selection.length() > 0) {
- selection += " OR ";
+ StringBuilder selection = new StringBuilder();
+ boolean usePosts = false;
+ for (String id : AlbumSettings.getEnabledAlbums(mSettings)) {
+ if (id.startsWith(TAG)) {
+ String[] parts = id.split(":");
+ if (parts.length > 1) {
+ if (selection.length() > 0) {
+ selection.append(" OR ");
+ }
+ if (PICASA_BUZZ_TYPE.equals(parts[1])) {
+ usePosts = true;
+ } else {
+ log(TAG, "adding on: " + parts[1]);
+ selection.append(PICASA_ALBUM_ID + " = '" + parts[1] + "'");
+ }
+ }
}
- selection += PICASA_ALBUM_ID + " = '" + arg + "'";
}
+ if (usePosts) {
+ for (String id : findPostIds()) {
+ if (selection.length() > 0) {
+ selection.append(" OR ");
+ }
+ log(TAG, "adding on: " + id);
+ selection.append(PICASA_ALBUM_ID + " = '" + id + "'");
+ }
+ }
+
+ if (selection.length() == 0) {
+ return foundImages;
+ }
+ log(TAG, "selection is: " + selection.toString());
+
Uri.Builder picasaUriBuilder = new Uri.Builder()
.scheme("content")
- .authority("com.google.android.gallery3d.GooglePhotoProvider")
- .appendPath("photos");
+ .authority(PICASA_AUTHORITY)
+ .appendPath(PICASA_PHOTO_PATH);
Cursor cursor = mResolver.query(picasaUriBuilder.build(),
- projection, selection, null, null);
+ projection, selection.toString(), null, null);
if (cursor != null) {
if (cursor.getCount() > howMany && mNextPosition == -1) {
mNextPosition =
@@ -92,7 +135,6 @@ public class PicasaSource extends PhotoSource {
while (foundImages.size() < howMany && !cursor.isAfterLast()) {
if (idIndex >= 0) {
ImageData data = new ImageData();
- data.type = TYPE;
data.id = cursor.getString(idIndex);
if (urlIndex >= 0) {
@@ -116,16 +158,118 @@ public class PicasaSource extends PhotoSource {
return foundImages;
}
+ private Collection<String> findPostIds() {
+ LinkedList<String> postIds = new LinkedList<String>();
+ String[] projection = {PICASA_ID, PICASA_ALBUM_TYPE, PICASA_ALBUM_UPDATED};
+ String order = PICASA_ALBUM_UPDATED + " DESC";
+ Uri.Builder picasaUriBuilder = new Uri.Builder()
+ .scheme("content")
+ .authority(PICASA_AUTHORITY)
+ .appendPath(PICASA_ALBUM_PATH)
+ .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_IMAGE_VALUE);
+ Cursor cursor = mResolver.query(picasaUriBuilder.build(),
+ projection, null, null, order);
+ if (cursor != null) {
+ cursor.moveToFirst();
+
+ int idIndex = cursor.getColumnIndex(PICASA_ID);
+ int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE);
+
+ if (idIndex < 0) {
+ log(TAG, "can't find the ID column!");
+ } else {
+ while (postIds.size() < mMaxPostAblums && !cursor.isAfterLast()) {
+ if (typeIndex >= 0 && PICASA_BUZZ_TYPE.equals(cursor.getString(typeIndex))) {
+ postIds.add(cursor.getString(idIndex));
+ }
+ cursor.moveToNext();
+ }
+ }
+ cursor.close();
+ }
+ return postIds;
+ }
+
+ @Override
+ public Collection<AlbumData> findAlbums() {
+ log(TAG, "finding albums");
+ HashMap<String, AlbumData> foundAlbums = new HashMap<String, AlbumData>();
+ String[] projection = {PICASA_ID, PICASA_TITLE, PICASA_THUMB, PICASA_ALBUM_TYPE,
+ PICASA_ALBUM_UPDATED};
+ Uri.Builder picasaUriBuilder = new Uri.Builder()
+ .scheme("content")
+ .authority(PICASA_AUTHORITY)
+ .appendPath(PICASA_ALBUM_PATH)
+ .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_IMAGE_VALUE);
+ Cursor cursor = mResolver.query(picasaUriBuilder.build(),
+ projection, null, null, null);
+ if (cursor != null) {
+ cursor.moveToFirst();
+
+ int idIndex = cursor.getColumnIndex(PICASA_ID);
+ int thumbIndex = cursor.getColumnIndex(PICASA_THUMB);
+ int titleIndex = cursor.getColumnIndex(PICASA_TITLE);
+ int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE);
+ int updatedIndex = cursor.getColumnIndex(PICASA_ALBUM_UPDATED);
+
+ if (idIndex < 0) {
+ log(TAG, "can't find the ID column!");
+ } else {
+ while (!cursor.isAfterLast()) {
+ String id = TAG + ":" + cursor.getString(idIndex);
+ boolean isBuzz = (typeIndex >= 0 &&
+ PICASA_BUZZ_TYPE.equals(cursor.getString(typeIndex)));
+
+ if (isBuzz) {
+ id = TAG + ":" + PICASA_BUZZ_TYPE;
+ }
+
+ if (foundAlbums.get(id) == null) {
+ AlbumData data = new AlbumData();
+ data.id = id;
+
+ if (isBuzz) {
+ data.title =
+ mResources.getString(R.string.posts_album_name, "Posts");
+ } else if (titleIndex >= 0) {
+ data.title = cursor.getString(titleIndex);
+ } else {
+ data.title =
+ mResources.getString(R.string.unknown_album_name, "Unknown");
+ }
+
+ if (updatedIndex >= 0) {
+ data.updated = cursor.getLong(updatedIndex);
+ }
+
+ if (thumbIndex >= 0) {
+ data.thumbnailUrl = cursor.getString(thumbIndex);
+ }
+
+ log(TAG, "found " + data.title + "(" + data.id + ")");
+ foundAlbums.put(id, data);
+ }
+
+ cursor.moveToNext();
+ }
+ }
+ cursor.close();
+
+ }
+ log(TAG, "found " + foundAlbums.size() + " items.");
+ return foundAlbums.values();
+ }
+
@Override
protected InputStream getStream(ImageData data) {
InputStream is = null;
try {
Uri.Builder photoUriBuilder = new Uri.Builder()
.scheme("content")
- .authority("com.google.android.gallery3d.GooglePhotoProvider")
- .appendPath("photos")
+ .authority(PICASA_AUTHORITY)
+ .appendPath(PICASA_PHOTO_PATH)
.appendPath(data.id)
- .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_THUMB_VALUE);
+ .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_FULL_VALUE);
if (data.url != null) {
photoUriBuilder.appendQueryParameter(PICASA_URL_KEY, data.url);
}
diff --git a/src/com/android/dreams/phototable/StockSource.java b/src/com/android/dreams/phototable/StockSource.java
index 63e834b..d7c6d23 100644
--- a/src/com/android/dreams/phototable/StockSource.java
+++ b/src/com/android/dreams/phototable/StockSource.java
@@ -16,6 +16,7 @@
package com.android.dreams.phototable;
import android.content.Context;
+import android.content.SharedPreferences;
import android.util.Log;
import java.io.InputStream;
@@ -26,6 +27,7 @@ import java.util.LinkedList;
* Picks a random image from the local store.
*/
public class StockSource extends PhotoSource {
+ public static final String ALBUM_ID = "com.android.dreams.phototable.StockSource";
private static final String TAG = "PhotoTable.StockSource";
private static final int[] PHOTOS = {R.drawable.photo_044_002,
R.drawable.photo_039_002,
@@ -42,23 +44,36 @@ public class StockSource extends PhotoSource {
};
private final LinkedList<ImageData> mImageList;
- private int mNextPosition;
+ private final LinkedList<AlbumData> mAlbumList;
- public static final int TYPE = 1;
+ private int mNextPosition;
- public StockSource(Context context) {
- super(context);
+ public StockSource(Context context, SharedPreferences settings) {
+ super(context, settings);
mSourceName = TAG;
mImageList = new LinkedList<ImageData>();
+ mAlbumList = new LinkedList<AlbumData>();
fillQueue();
}
@Override
+ public Collection<AlbumData> findAlbums() {
+ if (mAlbumList.isEmpty()) {
+ AlbumData data = new AlbumData();
+ data.id = ALBUM_ID;
+ data.title = mResources.getString(R.string.stock_photo_album_name, "Default Photos");
+ data.thumbnailUrl = mResources.getString(R.string.stock_photo_thumbnail_url);
+ mAlbumList.offer(data);
+ }
+ log(TAG, "returning a list of albums: " + mAlbumList.size());
+ return mAlbumList;
+ }
+
+ @Override
protected Collection<ImageData> findImages(int howMany) {
if (mImageList.isEmpty()) {
for (int i = 0; i < PHOTOS.length; i++) {
ImageData data = new ImageData();
- data.type = TYPE;
data.id = Integer.toString(PHOTOS[i]);
mImageList.offer(data);
}
diff --git a/src/com/android/dreams/phototable/Table.java b/src/com/android/dreams/phototable/Table.java
deleted file mode 100644
index d673175..0000000
--- a/src/com/android/dreams/phototable/Table.java
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.dreams.phototable;
-
-import android.service.dreams.Dream;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.PointF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.AsyncTask;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
-import android.widget.ImageView;
-
-import java.util.LinkedList;
-import java.util.Random;
-
-/**
- * A surface where photos sit.
- */
-public class Table extends FrameLayout {
- private static final String TAG = "PhotoTable.Table";
- private static final boolean DEBUG = false;
-
- class Launcher implements Runnable {
- private final Table mTable;
- public Launcher(Table table) {
- mTable = table;
- }
-
- @Override
- public void run() {
- mTable.scheduleNext(mDropPeriod);
- mTable.launch();
- }
- }
-
- private static final long MAX_SELECTION_TIME = 10000L;
- private static Random sRNG = new Random();
-
- private final Launcher mLauncher;
- private final LinkedList<View> mOnTable;
- private final Dream mDream;
- private final int mDropPeriod;
- private final int mFastDropPeriod;
- private final int mNowDropDelay;
- private final float mImageRatio;
- private final float mTableRatio;
- private final float mImageRotationLimit;
- private final boolean mTapToExit;
- private final int mTableCapacity;
- private final int mInset;
- private final PhotoSourcePlexor mPhotoSource;
- private final Resources mResources;
- private PhotoLaunchTask mPhotoLaunchTask;
- private boolean mStarted;
- private boolean mIsLandscape;
- private BitmapFactory.Options mOptions;
- private int mLongSide;
- private int mShortSide;
- private int mWidth;
- private int mHeight;
- private View mSelected;
- private long mSelectedTime;
-
- public Table(Dream dream, AttributeSet as) {
- super(dream, as);
- mDream = dream;
- mResources = getResources();
- setBackground(mResources.getDrawable(R.drawable.table));
- mInset = mResources.getDimensionPixelSize(R.dimen.photo_inset);
- mDropPeriod = mResources.getInteger(R.integer.drop_period);
- mFastDropPeriod = mResources.getInteger(R.integer.fast_drop);
- mNowDropDelay = mResources.getInteger(R.integer.now_drop);
- mImageRatio = mResources.getInteger(R.integer.image_ratio) / 1000000f;
- mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f;
- mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation);
- mTableCapacity = mResources.getInteger(R.integer.table_capacity);
- mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit);
- mOnTable = new LinkedList<View>();
- mOptions = new BitmapFactory.Options();
- mOptions.inTempStorage = new byte[32768];
- mPhotoSource = new PhotoSourcePlexor(getContext());
- mLauncher = new Launcher(this);
- mStarted = false;
- }
-
- public boolean hasSelection() {
- return mSelected != null;
- }
-
- public View getSelected() {
- return mSelected;
- }
-
- public void clearSelection() {
- mSelected = null;
- }
-
- public void setSelection(View selected) {
- assert(selected != null);
- if (mSelected != null) {
- dropOnTable(mSelected);
- }
- mSelected = selected;
- mSelectedTime = System.currentTimeMillis();
- bringChildToFront(selected);
- pickUp(selected);
- }
-
- static float lerp(float a, float b, float f) {
- return (b-a)*f + a;
- }
-
- static float randfrange(float a, float b) {
- return lerp(a, b, sRNG.nextFloat());
- }
-
- static PointF randFromCurve(float t, PointF[] v) {
- PointF p = new PointF();
- if (v.length == 4 && t >= 0f && t <= 1f) {
- float a = (float) Math.pow(1f-t, 3f);
- float b = (float) Math.pow(1f-t, 2f) * t;
- float c = (1f-t) * (float) Math.pow(t, 2f);
- float d = (float) Math.pow(t, 3f);
-
- p.x = a * v[0].x + 3 * b * v[1].x + 3 * c * v[2].x + d * v[3].x;
- p.y = a * v[0].y + 3 * b * v[1].y + 3 * c * v[2].y + d * v[3].y;
- }
- return p;
- }
-
- private static PointF randInCenter(float i, float j, int width, int height) {
- log("randInCenter (" + i + ", " + j + ", " + width + ", " + height + ")");
- PointF p = new PointF();
- p.x = 0.5f * width + 0.15f * width * i;
- p.y = 0.5f * height + 0.15f * height * j;
- log("randInCenter returning " + p.x + "," + p.y);
- return p;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- if (hasSelection()) {
- dropOnTable(getSelected());
- clearSelection();
- } else {
- if (mTapToExit) {
- mDream.finish();
- }
- }
- return true;
- }
- return false;
- }
-
- @Override
- public void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- log("onLayout (" + left + ", " + top + ", " + right + ", " + bottom + ")");
-
- mHeight = bottom - top;
- mWidth = right - left;
-
- mLongSide = (int) (mImageRatio * Math.max(mWidth, mHeight));
- mShortSide = (int) (mImageRatio * Math.min(mWidth, mHeight));
-
- boolean isLandscape = mWidth > mHeight;
- if (mIsLandscape != isLandscape) {
- for (View photo: mOnTable) {
- if (photo == getSelected()) {
- pickUp(photo);
- } else {
- dropOnTable(photo);
- }
- }
- mIsLandscape = isLandscape;
- }
- start();
- }
-
- @Override
- public boolean isOpaque() {
- return true;
- }
-
- private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
- private int mTries;
- public PhotoLaunchTask() {
- mTries = 0;
- }
-
- @Override
- public View doInBackground(Void... unused) {
- log("load a new photo");
- LayoutInflater inflater = (LayoutInflater) Table.this.getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View photo = inflater.inflate(R.layout.photo, null);
- ImageView image = (ImageView) photo;
- Drawable[] layers = new Drawable[2];
- Bitmap decodedPhoto = Table.this.mPhotoSource.next(Table.this.mOptions,
- Table.this.mLongSide, Table.this.mShortSide);
- int photoWidth = Table.this.mOptions.outWidth;
- int photoHeight = Table.this.mOptions.outHeight;
- if (Table.this.mOptions.outWidth <= 0 || Table.this.mOptions.outHeight <= 0) {
- photo = null;
- } else {
- layers[0] = new BitmapDrawable(Table.this.mResources, decodedPhoto);
- layers[1] = Table.this.mResources.getDrawable(R.drawable.frame);
- LayerDrawable layerList = new LayerDrawable(layers);
- layerList.setLayerInset(0, Table.this.mInset, Table.this.mInset,
- Table.this.mInset, Table.this.mInset);
- image.setImageDrawable(layerList);
-
- photo.setTag(R.id.photo_width, new Integer(photoWidth));
- photo.setTag(R.id.photo_height, new Integer(photoHeight));
-
- photo.setOnTouchListener(new PhotoTouchListener(Table.this.getContext(),
- Table.this));
- }
-
- return photo;
- }
-
- @Override
- public void onPostExecute(View photo) {
- if (photo != null) {
- Table.this.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
- if (Table.this.hasSelection()) {
- Table.this.bringChildToFront(Table.this.getSelected());
- }
- int width = ((Integer) photo.getTag(R.id.photo_width)).intValue();
- int height = ((Integer) photo.getTag(R.id.photo_height)).intValue();
-
- log("drop it");
- Table.this.throwOnTable(photo);
-
- if(Table.this.mOnTable.size() < Table.this.mTableCapacity) {
- Table.this.scheduleNext(Table.this.mFastDropPeriod);
- }
- } else if (mTries < 3) {
- mTries++;
- this.execute();
- }
- }
- };
-
- public void launch() {
- log("launching");
- setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
- if (hasSelection() &&
- (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) {
- dropOnTable(getSelected());
- clearSelection();
- } else {
- log("inflate it");
- if (mPhotoLaunchTask == null ||
- mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
- mPhotoLaunchTask = new PhotoLaunchTask();
- mPhotoLaunchTask.execute();
- }
- }
- }
- public void fadeAway(final View photo, final boolean replace) {
- // fade out of view
- mOnTable.remove(photo);
- photo.animate().cancel();
- photo.animate()
- .withLayer()
- .alpha(0f)
- .setDuration(1000)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- removeView(photo);
- recycle(photo);
- if (replace) {
- scheduleNext(mNowDropDelay);
- }
- }
- });
- }
-
- public void moveToBackOfQueue(View photo) {
- // make this photo the last to be removed.
- bringChildToFront(photo);
- invalidate();
- mOnTable.remove(photo);
- mOnTable.offer(photo);
- }
-
- private void throwOnTable(final View photo) {
- mOnTable.offer(photo);
- log("start offscreen");
- int width = ((Integer) photo.getTag(R.id.photo_width));
- int height = ((Integer) photo.getTag(R.id.photo_height));
- photo.setRotation(-100.0f);
- photo.setX(-mLongSide);
- photo.setY(-mLongSide);
- dropOnTable(photo);
- }
-
- public void dropOnTable(final View photo) {
- float angle = randfrange(-mImageRotationLimit, mImageRotationLimit);
- PointF p = randInCenter((float) sRNG.nextGaussian(), (float) sRNG.nextGaussian(),
- mWidth, mHeight);
- float x = p.x;
- float y = p.y;
-
- log("drop it at " + x + ", " + y);
-
- float x0 = photo.getX();
- float y0 = photo.getY();
- float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
- float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
-
- x -= mTableRatio * mLongSide / 2f;
- y -= mTableRatio * mLongSide / 2f;
- log("fixed offset is " + x + ", " + y);
-
- float dx = x - x0;
- float dy = y - y0;
-
- float dist = (float) (Math.sqrt(dx * dx + dy * dy));
- int duration = (int) (1000f * dist / 400f);
- duration = Math.max(duration, 1000);
-
- log("animate it");
- // toss onto table
- photo.animate()
- .withLayer()
- .scaleX(mTableRatio / mImageRatio)
- .scaleY(mTableRatio / mImageRatio)
- .rotation(angle)
- .x(x)
- .y(y)
- .setDuration(duration)
- .setInterpolator(new DecelerateInterpolator(3f))
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- while (mOnTable.size() > mTableCapacity) {
- fadeAway(mOnTable.poll(), false);
- }
- }
- });
- }
-
- /** wrap all orientations to the interval [-180, 180). */
- private float wrapAngle(float angle) {
- float result = angle + 180;
- result = ((result % 360) + 360) % 360; // catch negative numbers
- result -= 180;
- return result;
- }
-
- private void pickUp(final View photo) {
- float photoWidth = photo.getWidth();
- float photoHeight = photo.getHeight();
-
- float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
-
- log("target it");
- float x = (getWidth() - photoWidth) / 2f;
- float y = (getHeight() - photoHeight) / 2f;
-
- float x0 = photo.getX();
- float y0 = photo.getY();
- float dx = x - x0;
- float dy = y - y0;
-
- float dist = (float) (Math.sqrt(dx * dx + dy * dy));
- int duration = (int) (1000f * dist / 1000f);
- duration = Math.max(duration, 500);
-
- photo.setRotation(wrapAngle(photo.getRotation()));
-
- log("animate it");
- // toss onto table
- photo.animate()
- .withLayer()
- .rotation(0f)
- .scaleX(scale)
- .scaleY(scale)
- .x(x)
- .y(y)
- .setDuration(duration)
- .setInterpolator(new DecelerateInterpolator(2f))
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- log("endtimes: " + photo.getX());
- }
- });
- }
-
- private void recycle(View photo) {
- ImageView image = (ImageView) photo;
- LayerDrawable layers = (LayerDrawable) image.getDrawable();
- BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0);
- bitmap.getBitmap().recycle();
- }
-
- public void start() {
- if (!mStarted) {
- log("kick it");
- mStarted = true;
- scheduleNext(mDropPeriod);
- launch();
- }
- }
-
- public void scheduleNext(int delay) {
- removeCallbacks(mLauncher);
- postDelayed(mLauncher, delay);
- }
-
- private static void log(String message) {
- if (DEBUG) {
- Log.i(TAG, message);
- }
- }
-}