diff options
author | Owen Lin <owenlin@google.com> | 2011-08-18 21:48:19 +0800 |
---|---|---|
committer | Owen Lin <owenlin@google.com> | 2011-08-24 16:49:06 +0800 |
commit | 5673202ac99b89112a82210016885a9355f7e991 (patch) | |
tree | 5e9d5322dcc4bfa0fe7926ea95f47c950a028ad1 /src/com/android/gallery3d/gadget | |
parent | 31c7b13bf1c93f7eabd658e55c545eccd9d347a9 (diff) | |
download | android_packages_apps_Snap-5673202ac99b89112a82210016885a9355f7e991.tar.gz android_packages_apps_Snap-5673202ac99b89112a82210016885a9355f7e991.tar.bz2 android_packages_apps_Snap-5673202ac99b89112a82210016885a9355f7e991.zip |
Fix shortcup broken issue.
fix: 5154308
Change-Id: I5cd2ef8efb84d4f356b3fe93106bddf10e0823cc
Diffstat (limited to 'src/com/android/gallery3d/gadget')
-rw-r--r-- | src/com/android/gallery3d/gadget/LocalPhotoSource.java | 202 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/MediaSetSource.java | 113 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java | 125 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetClickHandler.java | 59 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetConfigure.java | 167 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java | 245 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetService.java | 169 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetSource.java | 31 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetTypeChooser.java | 59 | ||||
-rw-r--r-- | src/com/android/gallery3d/gadget/WidgetUtils.java | 80 |
10 files changed, 1250 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/gadget/LocalPhotoSource.java b/src/com/android/gallery3d/gadget/LocalPhotoSource.java new file mode 100644 index 000000000..ad77de5b3 --- /dev/null +++ b/src/com/android/gallery3d/gadget/LocalPhotoSource.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.app.GalleryApp; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.ContentListener; +import com.android.gallery3d.data.DataManager; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.Path; +import com.android.gallery3d.util.GalleryUtils; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; +import android.provider.MediaStore.Images.Media; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Random; + +public class LocalPhotoSource implements WidgetSource { + + private static final String TAG = "LocalPhotoSource"; + + private static final int MAX_PHOTO_COUNT = 128; + + /* Static fields used to query for the correct set of images */ + private static final Uri CONTENT_URI = Media.EXTERNAL_CONTENT_URI; + private static final String DATE_TAKEN = Media.DATE_TAKEN; + private static final String[] PROJECTION = {Media._ID}; + private static final String[] COUNT_PROJECTION = {"count(*)"}; + /* We don't want to include the download directory */ + private static final String SELECTION = + String.format("%s != %s", Media.BUCKET_ID, getDownloadBucketId()); + private static final String ORDER = String.format("%s DESC", DATE_TAKEN); + + private Context mContext; + private ArrayList<Long> mPhotos = new ArrayList<Long>(); + private ContentListener mContentListener; + private ContentObserver mContentObserver; + private boolean mContentDirty = true; + private DataManager mDataManager; + private static final Path LOCAL_IMAGE_ROOT = Path.fromString("/local/image/item"); + + public LocalPhotoSource(Context context) { + mContext = context; + mDataManager = ((GalleryApp) context.getApplicationContext()).getDataManager(); + mContentObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + mContentDirty = true; + if (mContentListener != null) mContentListener.onContentDirty(); + } + }; + mContext.getContentResolver() + .registerContentObserver(CONTENT_URI, true, mContentObserver); + } + + public void close() { + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + } + + @Override + public Uri getContentUri(int index) { + if (index < mPhotos.size()) { + return CONTENT_URI.buildUpon() + .appendPath(String.valueOf(mPhotos.get(index))) + .build(); + } + return null; + } + + @Override + public Bitmap getImage(int index) { + if (index >= mPhotos.size()) return null; + long id = mPhotos.get(index); + MediaItem image = (MediaItem) + mDataManager.getMediaObject(LOCAL_IMAGE_ROOT.getChild(id)); + if (image == null) return null; + + return WidgetUtils.createWidgetBitmap(image); + } + + private int[] getExponentialIndice(int total, int count) { + Random random = new Random(); + if (count > total) count = total; + HashSet<Integer> selected = new HashSet<Integer>(count); + while (selected.size() < count) { + int row = (int)(-Math.log(random.nextDouble()) * total / 2); + if (row < total) selected.add(row); + } + int values[] = new int[count]; + int index = 0; + for (int value : selected) { + values[index++] = value; + } + return values; + } + + private int getPhotoCount(ContentResolver resolver) { + Cursor cursor = resolver.query( + CONTENT_URI, COUNT_PROJECTION, SELECTION, null, null); + if (cursor == null) return 0; + try { + Utils.assertTrue(cursor.moveToNext()); + return cursor.getInt(0); + } finally { + cursor.close(); + } + } + + private boolean isContentSound(int totalCount) { + if (mPhotos.size() < Math.min(totalCount, MAX_PHOTO_COUNT)) return false; + if (mPhotos.size() == 0) return true; // totalCount is also 0 + + StringBuilder builder = new StringBuilder(); + for (Long imageId : mPhotos) { + if (builder.length() > 0) builder.append(","); + builder.append(imageId); + } + Cursor cursor = mContext.getContentResolver().query( + CONTENT_URI, COUNT_PROJECTION, + String.format("%s in (%s)", Media._ID, builder.toString()), + null, null); + if (cursor == null) return false; + try { + Utils.assertTrue(cursor.moveToNext()); + return cursor.getInt(0) == mPhotos.size(); + } finally { + cursor.close(); + } + } + + public void reload() { + if (!mContentDirty) return; + mContentDirty = false; + + ContentResolver resolver = mContext.getContentResolver(); + int photoCount = getPhotoCount(resolver); + if (isContentSound(photoCount)) return; + + int choosedIds[] = getExponentialIndice(photoCount, MAX_PHOTO_COUNT); + Arrays.sort(choosedIds); + + mPhotos.clear(); + Cursor cursor = mContext.getContentResolver().query( + CONTENT_URI, PROJECTION, SELECTION, null, ORDER); + if (cursor == null) return; + try { + for (int index : choosedIds) { + if (cursor.moveToPosition(index)) { + mPhotos.add(cursor.getLong(0)); + } + } + } finally { + cursor.close(); + } + } + + @Override + public int size() { + reload(); + return mPhotos.size(); + } + + /** + * Builds the bucket ID for the public external storage Downloads directory + * @return the bucket ID + */ + private static int getDownloadBucketId() { + String downloadsPath = Environment + .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + .getAbsolutePath(); + return GalleryUtils.getBucketId(downloadsPath); + } + + @Override + public void setContentListener(ContentListener listener) { + mContentListener = listener; + } +} diff --git a/src/com/android/gallery3d/gadget/MediaSetSource.java b/src/com/android/gallery3d/gadget/MediaSetSource.java new file mode 100644 index 000000000..c1687e0c2 --- /dev/null +++ b/src/com/android/gallery3d/gadget/MediaSetSource.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.ContentListener; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.MediaObject; +import com.android.gallery3d.data.MediaSet; + +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Binder; + +import java.util.ArrayList; +import java.util.Arrays; + +public class MediaSetSource implements WidgetSource, ContentListener { + private static final int CACHE_SIZE = 32; + + private static final String TAG = "MediaSetSource"; + + private MediaSet mSource; + private MediaItem mCache[] = new MediaItem[CACHE_SIZE]; + private int mCacheStart; + private int mCacheEnd; + private long mSourceVersion = MediaObject.INVALID_DATA_VERSION; + + private ContentListener mContentListener; + + public MediaSetSource(MediaSet source) { + mSource = Utils.checkNotNull(source); + mSource.addContentListener(this); + } + + @Override + public void close() { + mSource.removeContentListener(this); + } + + private void ensureCacheRange(int index) { + if (index >= mCacheStart && index < mCacheEnd) return; + + long token = Binder.clearCallingIdentity(); + try { + mCacheStart = index; + ArrayList<MediaItem> items = mSource.getMediaItem(mCacheStart, CACHE_SIZE); + mCacheEnd = mCacheStart + items.size(); + items.toArray(mCache); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public synchronized Uri getContentUri(int index) { + ensureCacheRange(index); + if (index < mCacheStart || index >= mCacheEnd) return null; + return mCache[index - mCacheStart].getContentUri(); + } + + @Override + public synchronized Bitmap getImage(int index) { + ensureCacheRange(index); + if (index < mCacheStart || index >= mCacheEnd) return null; + return WidgetUtils.createWidgetBitmap(mCache[index - mCacheStart]); + } + + @Override + public void reload() { + long version = mSource.reload(); + if (mSourceVersion != version) { + mSourceVersion = version; + mCacheStart = 0; + mCacheEnd = 0; + Arrays.fill(mCache, null); + } + } + + @Override + public void setContentListener(ContentListener listener) { + mContentListener = listener; + } + + @Override + public int size() { + long token = Binder.clearCallingIdentity(); + try { + return mSource.getMediaItemCount(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onContentDirty() { + if (mContentListener != null) mContentListener.onContentDirty(); + } +} diff --git a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java new file mode 100644 index 000000000..814ede25f --- /dev/null +++ b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.R; +import com.android.gallery3d.gadget.WidgetDatabaseHelper.Entry; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.util.Log; +import android.widget.RemoteViews; + +public class PhotoAppWidgetProvider extends AppWidgetProvider { + + private static final String TAG = "WidgetProvider"; + + static RemoteViews buildWidget(Context context, int id, Entry entry) { + + switch (entry.type) { + case WidgetDatabaseHelper.TYPE_ALBUM: + case WidgetDatabaseHelper.TYPE_SHUFFLE: + return buildStackWidget(context, id, entry); + case WidgetDatabaseHelper.TYPE_SINGLE_PHOTO: + return buildFrameWidget(context, id, entry); + } + throw new RuntimeException("invalid type - " + entry.type); + } + + @Override + public void onUpdate(Context context, + AppWidgetManager appWidgetManager, int[] appWidgetIds) { + WidgetDatabaseHelper helper = new WidgetDatabaseHelper(context); + try { + for (int id : appWidgetIds) { + Entry entry = helper.getEntry(id); + if (entry != null) { + RemoteViews views = buildWidget(context, id, entry); + appWidgetManager.updateAppWidget(id, views); + } else { + Log.e(TAG, "cannot load widget: " + id); + } + } + } finally { + helper.close(); + } + super.onUpdate(context, appWidgetManager, appWidgetIds); + } + + private static RemoteViews buildStackWidget(Context context, int widgetId, Entry entry) { + RemoteViews views = new RemoteViews( + context.getPackageName(), R.layout.appwidget_main); + + Intent intent = new Intent(context, WidgetService.class); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); + intent.putExtra(WidgetService.EXTRA_WIDGET_TYPE, entry.type); + intent.putExtra(WidgetService.EXTRA_ALBUM_PATH, entry.albumPath); + intent.setData(Uri.parse("widget://gallery/" + widgetId)); + + views.setRemoteAdapter(R.id.appwidget_stack_view, intent); + views.setEmptyView(R.id.appwidget_stack_view, R.id.appwidget_empty_view); + + Intent clickIntent = new Intent(context, WidgetClickHandler.class); + PendingIntent pendingIntent = PendingIntent.getActivity( + context, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT); + views.setPendingIntentTemplate(R.id.appwidget_stack_view, pendingIntent); + + return views; + } + + static RemoteViews buildFrameWidget(Context context, int appWidgetId, Entry entry) { + RemoteViews views = new RemoteViews( + context.getPackageName(), R.layout.photo_frame); + try { + byte[] data = entry.imageData; + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + views.setImageViewBitmap(R.id.photo, bitmap); + } catch (Throwable t) { + Log.w(TAG, "cannot load widget image: " + appWidgetId, t); + } + + if (entry.imageUri != null) { + try { + Uri uri = Uri.parse(entry.imageUri); + Intent clickIntent = new Intent(context, WidgetClickHandler.class) + .setData(uri); + PendingIntent pendingClickIntent = PendingIntent.getActivity(context, 0, + clickIntent, PendingIntent.FLAG_CANCEL_CURRENT); + views.setOnClickPendingIntent(R.id.photo, pendingClickIntent); + } catch (Throwable t) { + Log.w(TAG, "cannot load widget uri: " + appWidgetId, t); + } + } + return views; + } + + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + // Clean deleted photos out of our database + WidgetDatabaseHelper helper = new WidgetDatabaseHelper(context); + for (int appWidgetId : appWidgetIds) { + helper.deleteEntry(appWidgetId); + } + helper.close(); + } +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/gadget/WidgetClickHandler.java b/src/com/android/gallery3d/gadget/WidgetClickHandler.java new file mode 100644 index 000000000..075644d5e --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetClickHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.R; +import com.android.gallery3d.app.Gallery; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +public class WidgetClickHandler extends Activity { + private static final String TAG = "PhotoAppWidgetClickHandler"; + + private boolean isValidDataUri(Uri dataUri) { + if (dataUri == null) return false; + try { + AssetFileDescriptor f = getContentResolver() + .openAssetFileDescriptor(dataUri, "r"); + f.close(); + return true; + } catch (Throwable e) { + Log.w(TAG, "cannot open uri: " + dataUri, e); + return false; + } + } + + @Override + protected void onCreate(Bundle savedState) { + super.onCreate(savedState); + Intent intent = getIntent(); + if (isValidDataUri(intent.getData())) { + startActivity(new Intent(Intent.ACTION_VIEW, intent.getData())); + } else { + Toast.makeText(this, + R.string.no_such_item, Toast.LENGTH_LONG).show(); + startActivity(new Intent(this, Gallery.class)); + } + finish(); + } +} diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java new file mode 100644 index 000000000..747cc3a6d --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.R; +import com.android.gallery3d.app.AlbumPicker; +import com.android.gallery3d.app.CropImage; +import com.android.gallery3d.app.DialogPicker; + +import android.app.Activity; +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.widget.RemoteViews; + +public class WidgetConfigure extends Activity { + @SuppressWarnings("unused") + private static final String TAG = "WidgetConfigure"; + + public static final String KEY_WIDGET_TYPE = "widget-type"; + + private static final int REQUEST_WIDGET_TYPE = 1; + private static final int REQUEST_CHOOSE_ALBUM = 2; + private static final int REQUEST_CROP_IMAGE = 3; + private static final int REQUEST_GET_PHOTO = 4; + + public static final int RESULT_ERROR = RESULT_FIRST_USER; + + // Scale up the widget size since we only specified the minimized + // size of the gadget. The real size could be larger. + // Note: There is also a limit on the size of data that can be + // passed in Binder's transaction. + private static float WIDGET_SCALE_FACTOR = 1.5f; + + private int mAppWidgetId = -1; + private int mWidgetType = 0; + private Uri mPickedItem; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + mAppWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + + if (mAppWidgetId == -1) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } + + if (mWidgetType == 0) { + Intent intent = new Intent(this, WidgetTypeChooser.class); + startActivityForResult(intent, REQUEST_WIDGET_TYPE); + } + } + + private void updateWidgetAndFinish(WidgetDatabaseHelper.Entry entry) { + AppWidgetManager manager = AppWidgetManager.getInstance(this); + RemoteViews views = PhotoAppWidgetProvider.buildWidget(this, mAppWidgetId, entry); + manager.updateAppWidget(mAppWidgetId, views); + setResult(RESULT_OK, new Intent().putExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId)); + finish(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != RESULT_OK) { + setResult(resultCode, new Intent().putExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId)); + finish(); + return; + } + + if (requestCode == REQUEST_WIDGET_TYPE) { + setWidgetType(data); + } else if (requestCode == REQUEST_CHOOSE_ALBUM) { + setChoosenAlbum(data); + } else if (requestCode == REQUEST_GET_PHOTO) { + setChoosenPhoto(data); + } else if (requestCode == REQUEST_CROP_IMAGE) { + setPhotoWidget(data); + } else { + throw new AssertionError("unknown request: " + requestCode); + } + } + + private void setPhotoWidget(Intent data) { + // Store the cropped photo in our database + Bitmap bitmap = (Bitmap) data.getParcelableExtra("data"); + WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this); + try { + helper.setPhoto(mAppWidgetId, mPickedItem, bitmap); + updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); + } finally { + helper.close(); + } + } + + private void setChoosenPhoto(Intent data) { + Resources res = getResources(); + int widgetWidth = Math.round(WIDGET_SCALE_FACTOR + * res.getDimension(R.dimen.appwidget_width)); + int widgetHeight = Math.round(WIDGET_SCALE_FACTOR + * res.getDimension(R.dimen.appwidget_height)); + mPickedItem = data.getData(); + Intent request = new Intent(CropImage.ACTION_CROP, mPickedItem) + .putExtra(CropImage.KEY_OUTPUT_X, widgetWidth) + .putExtra(CropImage.KEY_OUTPUT_Y, widgetHeight) + .putExtra(CropImage.KEY_ASPECT_X, widgetWidth) + .putExtra(CropImage.KEY_ASPECT_Y, widgetHeight) + .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropImage.KEY_SCALE, true) + .putExtra(CropImage.KEY_RETURN_DATA, true); + startActivityForResult(request, REQUEST_CROP_IMAGE); + } + + private void setChoosenAlbum(Intent data) { + String albumPath = data.getStringExtra(AlbumPicker.KEY_ALBUM_PATH); + WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this); + try { + helper.setWidget(mAppWidgetId, + WidgetDatabaseHelper.TYPE_ALBUM, albumPath); + updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); + } finally { + helper.close(); + } + } + + private void setWidgetType(Intent data) { + mWidgetType = data.getIntExtra(KEY_WIDGET_TYPE, R.id.widget_type_shuffle); + if (mWidgetType == R.id.widget_type_album) { + Intent intent = new Intent(this, AlbumPicker.class); + startActivityForResult(intent, REQUEST_CHOOSE_ALBUM); + } else if (mWidgetType == R.id.widget_type_shuffle) { + WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this); + try { + helper.setWidget(mAppWidgetId, WidgetDatabaseHelper.TYPE_SHUFFLE, null); + updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); + } finally { + helper.close(); + } + } else { + // Explicitly send the intent to the DialogPhotoPicker + Intent request = new Intent(this, DialogPicker.class) + .setAction(Intent.ACTION_GET_CONTENT) + .setType("image/*"); + startActivityForResult(request, REQUEST_GET_PHOTO); + } + } +} diff --git a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java new file mode 100644 index 000000000..1d0754808 --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.common.Utils; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; + +public class WidgetDatabaseHelper extends SQLiteOpenHelper { + private static final String TAG = "PhotoDatabaseHelper"; + private static final String DATABASE_NAME = "launcher.db"; + + private static final int DATABASE_VERSION = 4; + + private static final String TABLE_WIDGETS = "widgets"; + + private static final String FIELD_APPWIDGET_ID = "appWidgetId"; + private static final String FIELD_IMAGE_URI = "imageUri"; + private static final String FIELD_PHOTO_BLOB = "photoBlob"; + private static final String FIELD_WIDGET_TYPE = "widgetType"; + private static final String FIELD_ALBUM_PATH = "albumPath"; + + public static final int TYPE_SINGLE_PHOTO = 0; + public static final int TYPE_SHUFFLE = 1; + public static final int TYPE_ALBUM = 2; + + private static final String[] PROJECTION = { + FIELD_WIDGET_TYPE, FIELD_IMAGE_URI, FIELD_PHOTO_BLOB, FIELD_ALBUM_PATH}; + private static final int INDEX_WIDGET_TYPE = 0; + private static final int INDEX_IMAGE_URI = 1; + private static final int INDEX_PHOTO_BLOB = 2; + private static final int INDEX_ALBUM_PATH = 3; + private static final String WHERE_CLAUSE = FIELD_APPWIDGET_ID + " = ?"; + + public static class Entry { + public int widgetId; + public int type; + public String imageUri; + public byte imageData[]; + public String albumPath; + + private Entry() {} + + private Entry(int id, Cursor cursor) { + widgetId = id; + type = cursor.getInt(INDEX_WIDGET_TYPE); + if (type == TYPE_SINGLE_PHOTO) { + imageUri = cursor.getString(INDEX_IMAGE_URI); + imageData = cursor.getBlob(INDEX_PHOTO_BLOB); + } else if (type == TYPE_ALBUM) { + albumPath = cursor.getString(INDEX_ALBUM_PATH); + } + } + } + + public WidgetDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_WIDGETS + " (" + + FIELD_APPWIDGET_ID + " INTEGER PRIMARY KEY, " + + FIELD_WIDGET_TYPE + " INTEGER DEFAULT 0, " + + FIELD_IMAGE_URI + " TEXT, " + + FIELD_ALBUM_PATH + " TEXT, " + + FIELD_PHOTO_BLOB + " BLOB)"); + } + + private void saveData(SQLiteDatabase db, int oldVersion, ArrayList<Entry> data) { + if (oldVersion <= 2) { + Cursor cursor = db.query("photos", + new String[] {FIELD_APPWIDGET_ID, FIELD_PHOTO_BLOB}, + null, null, null, null, null); + if (cursor == null) return; + try { + while (cursor.moveToNext()) { + Entry entry = new Entry(); + entry.type = TYPE_SINGLE_PHOTO; + entry.widgetId = cursor.getInt(0); + entry.imageData = cursor.getBlob(1); + data.add(entry); + } + } finally { + cursor.close(); + } + } else if (oldVersion == 3) { + Utils.debug("saveData of version: %s", oldVersion); + Cursor cursor = db.query("photos", + new String[] {FIELD_APPWIDGET_ID, FIELD_PHOTO_BLOB, FIELD_IMAGE_URI}, + null, null, null, null, null); + if (cursor == null) return; + try { + while (cursor.moveToNext()) { + Entry entry = new Entry(); + entry.type = TYPE_SINGLE_PHOTO; + entry.widgetId = cursor.getInt(0); + entry.imageData = cursor.getBlob(1); + entry.imageUri = cursor.getString(2); + + Utils.debug("store widget[%s] - %s", entry.widgetId, entry.imageUri); + data.add(entry); + } + } finally { + cursor.close(); + } + } + } + + private void restoreData(SQLiteDatabase db, ArrayList<Entry> data) { + db.beginTransaction(); + try { + for (Entry entry : data) { + ContentValues values = new ContentValues(); + values.put(FIELD_APPWIDGET_ID, entry.widgetId); + values.put(FIELD_WIDGET_TYPE, entry.type); + values.put(FIELD_IMAGE_URI, entry.imageUri); + values.put(FIELD_PHOTO_BLOB, entry.imageData); + values.put(FIELD_ALBUM_PATH, entry.albumPath); + db.insert(TABLE_WIDGETS, null, values); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + int version = oldVersion; + + if (version != DATABASE_VERSION) { + ArrayList<Entry> data = new ArrayList<Entry>(); + saveData(db, oldVersion, data); + + Log.w(TAG, "destroying all old data."); + // Table "photos" is renamed to "widget" in version 4 + db.execSQL("DROP TABLE IF EXISTS photos"); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_WIDGETS); + onCreate(db); + + restoreData(db, data); + } + } + + /** + * Store the given bitmap in this database for the given appWidgetId. + */ + public boolean setPhoto(int appWidgetId, Uri imageUri, Bitmap bitmap) { + try { + // Try go guesstimate how much space the icon will take when + // serialized to avoid unnecessary allocations/copies during + // the write. + int size = bitmap.getWidth() * bitmap.getHeight() * 4; + ByteArrayOutputStream out = new ByteArrayOutputStream(size); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + out.close(); + + ContentValues values = new ContentValues(); + values.put(FIELD_APPWIDGET_ID, appWidgetId); + values.put(FIELD_WIDGET_TYPE, TYPE_SINGLE_PHOTO); + values.put(FIELD_IMAGE_URI, imageUri.toString()); + values.put(FIELD_PHOTO_BLOB, out.toByteArray()); + + SQLiteDatabase db = getWritableDatabase(); + db.replaceOrThrow(TABLE_WIDGETS, null, values); + return true; + } catch (Throwable e) { + Log.e(TAG, "set widget photo fail", e); + return false; + } + } + + public boolean setWidget(int id, int type, String albumPath) { + try { + ContentValues values = new ContentValues(); + values.put(FIELD_APPWIDGET_ID, id); + values.put(FIELD_WIDGET_TYPE, type); + values.put(FIELD_ALBUM_PATH, Utils.ensureNotNull(albumPath)); + getWritableDatabase().replaceOrThrow(TABLE_WIDGETS, null, values); + return true; + } catch (Throwable e) { + Log.e(TAG, "set widget fail", e); + return false; + } + } + + public Entry getEntry(int appWidgetId) { + Cursor cursor = null; + try { + SQLiteDatabase db = getReadableDatabase(); + cursor = db.query(TABLE_WIDGETS, PROJECTION, + WHERE_CLAUSE, new String[] {String.valueOf(appWidgetId)}, + null, null, null); + if (cursor == null || !cursor.moveToNext()) { + Log.e(TAG, "query fail: empty cursor: " + cursor); + return null; + } + return new Entry(appWidgetId, cursor); + } catch (Throwable e) { + Log.e(TAG, "Could not load photo from database", e); + return null; + } finally { + Utils.closeSilently(cursor); + } + } + + /** + * Remove any bitmap associated with the given appWidgetId. + */ + public void deleteEntry(int appWidgetId) { + try { + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_WIDGETS, WHERE_CLAUSE, + new String[] {String.valueOf(appWidgetId)}); + } catch (SQLiteException e) { + Log.e(TAG, "Could not delete photo from database", e); + } + } +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/gadget/WidgetService.java b/src/com/android/gallery3d/gadget/WidgetService.java new file mode 100644 index 000000000..a61831ca6 --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetService.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.R; +import com.android.gallery3d.app.GalleryApp; +import com.android.gallery3d.data.ContentListener; +import com.android.gallery3d.data.DataManager; +import com.android.gallery3d.data.MediaSet; +import com.android.gallery3d.data.Path; + +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; + +public class WidgetService extends RemoteViewsService { + + @SuppressWarnings("unused") + private static final String TAG = "GalleryAppWidgetService"; + + public static final String EXTRA_WIDGET_TYPE = "widget-type"; + public static final String EXTRA_ALBUM_PATH = "album-path"; + + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + int id = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + int type = intent.getIntExtra(EXTRA_WIDGET_TYPE, 0); + String albumPath = intent.getStringExtra(EXTRA_ALBUM_PATH); + + return new PhotoRVFactory((GalleryApp) getApplicationContext(), + id, type, albumPath); + } + + private static class EmptySource implements WidgetSource { + + @Override + public int size() { + return 0; + } + + @Override + public Bitmap getImage(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public Uri getContentUri(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public void setContentListener(ContentListener listener) {} + + @Override + public void reload() {} + + @Override + public void close() {} + } + + private static class PhotoRVFactory implements + RemoteViewsService.RemoteViewsFactory, ContentListener { + + private final int mAppWidgetId; + private final int mType; + private final String mAlbumPath; + private final GalleryApp mApp; + + private WidgetSource mSource; + + public PhotoRVFactory(GalleryApp app, int id, int type, String albumPath) { + mApp = app; + mAppWidgetId = id; + mType = type; + mAlbumPath = albumPath; + } + + @Override + public void onCreate() { + if (mType == WidgetDatabaseHelper.TYPE_ALBUM) { + Path path = Path.fromString(mAlbumPath); + DataManager manager = mApp.getDataManager(); + MediaSet mediaSet = (MediaSet) manager.getMediaObject(path); + mSource = mediaSet == null + ? new EmptySource() + : new MediaSetSource(mediaSet); + } else { + mSource = new LocalPhotoSource(mApp.getAndroidContext()); + } + mSource.setContentListener(this); + AppWidgetManager.getInstance(mApp.getAndroidContext()) + .notifyAppWidgetViewDataChanged( + mAppWidgetId, R.id.appwidget_stack_view); + } + + @Override + public void onDestroy() { + mSource.close(); + mSource = null; + } + + public int getCount() { + return mSource.size(); + } + + public long getItemId(int position) { + return position; + } + + public int getViewTypeCount() { + return 1; + } + + public boolean hasStableIds() { + return true; + } + + public RemoteViews getLoadingView() { + RemoteViews rv = new RemoteViews( + mApp.getAndroidContext().getPackageName(), + R.layout.appwidget_loading_item); + rv.setProgressBar(R.id.appwidget_loading_item, 0, 0, true); + return rv; + } + + public RemoteViews getViewAt(int position) { + Bitmap bitmap = mSource.getImage(position); + if (bitmap == null) return getLoadingView(); + RemoteViews views = new RemoteViews( + mApp.getAndroidContext().getPackageName(), + R.layout.appwidget_photo_item); + views.setImageViewBitmap(R.id.appwidget_photo_item, bitmap); + views.setOnClickFillInIntent(R.id.appwidget_photo_item, new Intent() + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .setData(mSource.getContentUri(position))); + return views; + } + + @Override + public void onDataSetChanged() { + mSource.reload(); + } + + @Override + public void onContentDirty() { + AppWidgetManager.getInstance(mApp.getAndroidContext()) + .notifyAppWidgetViewDataChanged( + mAppWidgetId, R.id.appwidget_stack_view); + } + } +} diff --git a/src/com/android/gallery3d/gadget/WidgetSource.java b/src/com/android/gallery3d/gadget/WidgetSource.java new file mode 100644 index 000000000..8b8eb79dc --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetSource.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.data.ContentListener; + +import android.graphics.Bitmap; +import android.net.Uri; + +public interface WidgetSource { + public int size(); + public Bitmap getImage(int index); + public Uri getContentUri(int index); + public void setContentListener(ContentListener listener); + public void reload(); + public void close(); +} diff --git a/src/com/android/gallery3d/gadget/WidgetTypeChooser.java b/src/com/android/gallery3d/gadget/WidgetTypeChooser.java new file mode 100644 index 000000000..c4bca60d0 --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetTypeChooser.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.R; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.RadioGroup; +import android.widget.RadioGroup.OnCheckedChangeListener; + +public class WidgetTypeChooser extends Activity { + + private OnCheckedChangeListener mListener = new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + Intent data = new Intent() + .putExtra(WidgetConfigure.KEY_WIDGET_TYPE, checkedId); + setResult(RESULT_OK, data); + finish(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setTitle(R.string.widget_type); + setContentView(R.layout.choose_widget_type); + RadioGroup rg = (RadioGroup) findViewById(R.id.widget_type); + rg.setOnCheckedChangeListener(mListener); + + Button cancel = (Button) findViewById(R.id.cancel); + cancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }); + } +} diff --git a/src/com/android/gallery3d/gadget/WidgetUtils.java b/src/com/android/gallery3d/gadget/WidgetUtils.java new file mode 100644 index 000000000..b194c7d8e --- /dev/null +++ b/src/com/android/gallery3d/gadget/WidgetUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.gadget; + +import com.android.gallery3d.R; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.util.ThreadPool; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Bitmap.Config; +import android.util.Log; + +public class WidgetUtils { + + private static final String TAG = "WidgetUtils"; + + private static int sStackPhotoWidth = 220; + private static int sStackPhotoHeight = 170; + + private WidgetUtils() { + } + + public static void initialize(Context context) { + Resources r = context.getResources(); + sStackPhotoWidth = r.getDimensionPixelSize(R.dimen.stack_photo_width); + sStackPhotoHeight = r.getDimensionPixelSize(R.dimen.stack_photo_height); + } + + public static Bitmap createWidgetBitmap(MediaItem image) { + Bitmap bitmap = image.requestImage(MediaItem.TYPE_THUMBNAIL) + .run(ThreadPool.JOB_CONTEXT_STUB); + if (bitmap == null) { + Log.w(TAG, "fail to get image of " + image.toString()); + return null; + } + return createWidgetBitmap(bitmap, image.getRotation()); + } + + public static Bitmap createWidgetBitmap(Bitmap bitmap, int rotation) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + float scale; + if (((rotation / 90) & 1) == 0) { + scale = Math.max((float) sStackPhotoWidth / w, + (float) sStackPhotoHeight / h); + } else { + scale = Math.max((float) sStackPhotoWidth / h, + (float) sStackPhotoHeight / w); + } + + Bitmap target = Bitmap.createBitmap( + sStackPhotoWidth, sStackPhotoHeight, Config.ARGB_8888); + Canvas canvas = new Canvas(target); + canvas.translate(sStackPhotoWidth / 2, sStackPhotoHeight / 2); + canvas.rotate(rotation); + canvas.scale(scale, scale); + Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); + canvas.drawBitmap(bitmap, -w / 2, -h / 2, paint); + return target; + } +} |