OSDN Git Service

Add sharing support
authorBobby Georgescu <georgescu@google.com>
Thu, 7 Mar 2013 22:15:57 +0000 (14:15 -0800)
committerBobby Georgescu <georgescu@google.com>
Fri, 8 Mar 2013 23:41:15 +0000 (15:41 -0800)
Change-Id: I2c7f18b0d6dd057c1fba96c1133d563f32abf55c

gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
res/menu/gallery_multiselect.xml [new file with mode: 0644]
src/com/android/photos/GalleryActivity.java
src/com/android/photos/PhotoSetFragment.java
src/com/android/photos/SelectionManager.java [new file with mode: 0644]
src/com/android/photos/adapters/PhotoThumbnailAdapter.java [new file with mode: 0644]
src/com/android/photos/data/PhotoSetLoader.java
src/com/android/photos/shims/BitmapJobDrawable.java
src/com/android/photos/shims/MediaItemsLoader.java

index 4200ec7..13d48fd 100644 (file)
@@ -39,6 +39,8 @@ public class ApiHelper {
         public static final int JELLY_BEAN_MR1 = 17;
     }
 
+    public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16;
+
     public static final boolean USE_888_PIXEL_FORMAT =
             Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
 
diff --git a/res/menu/gallery_multiselect.xml b/res/menu/gallery_multiselect.xml
new file mode 100644 (file)
index 0000000..3fbb6a7
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@+id/menu_share"
+          android:title="@string/share"
+          android:showAsAction="ifRoom"
+          android:actionProviderClass="android.widget.ShareActionProvider" />
+</menu>
\ No newline at end of file
index 46b5140..2335658 100644 (file)
@@ -33,6 +33,7 @@ public class GalleryActivity extends Activity {
 
     private final String FTAG_PHOTOSET = "PhotoSet";
     private final String FTAG_ALBUMSET = "AlbumSet";
+    private SelectionManager mSelectionManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -41,6 +42,13 @@ public class GalleryActivity extends Activity {
         setupActionBar();
     }
 
+    protected SelectionManager getSelectionManager() {
+        if (mSelectionManager == null) {
+            mSelectionManager = new SelectionManager(this);
+        }
+        return mSelectionManager;
+    }
+
     private void setupActionBar() {
         ActionBar ab = getActionBar();
         ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
index 18587fc..25d8036 100644 (file)
@@ -18,46 +18,58 @@ package com.android.photos;
 
 import android.app.Fragment;
 import android.app.LoaderManager.LoaderCallbacks;
-import android.content.Context;
 import android.content.Intent;
 import android.content.Loader;
 import android.database.Cursor;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
+import android.widget.AbsListView.MultiChoiceModeListener;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
-import android.widget.CursorAdapter;
 import android.widget.GridView;
-import android.widget.ImageView;
+import android.widget.ShareActionProvider;
+import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.app.Gallery;
+import com.android.gallery3d.data.MediaItem;
+import com.android.photos.adapters.PhotoThumbnailAdapter;
 import com.android.photos.data.PhotoSetLoader;
 import com.android.photos.shims.LoaderCompatShim;
 import com.android.photos.shims.MediaItemsLoader;
-import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor>,
-        OnItemClickListener {
+        OnItemClickListener, SelectionManager.SelectedUriSource, MultiChoiceModeListener,
+        OnShareTargetSelectedListener {
 
     private static final int LOADER_PHOTOSET = 1;
 
     private GridView mPhotoSetView;
     private View mEmptyView;
-    private ThumbnailAdapter mAdapter;
+
     private boolean mInitialLoadComplete = false;
     private LoaderCompatShim<Cursor> mLoaderCompatShim;
+    private PhotoThumbnailAdapter mAdapter;
+    private SelectionManager mSelectionManager;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mAdapter = new ThumbnailAdapter(getActivity());
+        GalleryActivity activity = (GalleryActivity) getActivity();
+        mSelectionManager = activity.getSelectionManager();
+        mAdapter = new PhotoThumbnailAdapter(activity);
     }
 
     @Override
@@ -71,6 +83,8 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
         mEmptyView = root.findViewById(android.R.id.empty);
         mEmptyView.setVisibility(View.GONE);
         mPhotoSetView.setAdapter(mAdapter);
+        mPhotoSetView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);
+        mPhotoSetView.setMultiChoiceModeListener(this);
         getLoaderManager().initLoader(LOADER_PHOTOSET, null, this);
         updateEmptyStatus();
         return root;
@@ -101,9 +115,9 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         // TODO: Switch to PhotoSetLoader
         MediaItemsLoader loader = new MediaItemsLoader(getActivity());
-        mAdapter.setDrawableFactory(loader);
         mInitialLoadComplete = false;
         mLoaderCompatShim = loader;
+        mAdapter.setDrawableFactory(mLoaderCompatShim);
         return loader;
     }
 
@@ -115,54 +129,112 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor
         updateEmptyStatus();
     }
 
+    private Set<Uri> mSelectedUris = new HashSet<Uri>();
+    private ArrayList<Uri> mSelectedUrisArray = new ArrayList<Uri>();
+
+    @Override
+    public ArrayList<Uri> getSelectedShareableUris() {
+        mSelectedUrisArray.clear();
+        mSelectedUrisArray.addAll(mSelectedUris);
+        return mSelectedUrisArray;
+    }
+
+    public ArrayList<Uri> getSelectedShareableUrisUncached() {
+        mSelectedUrisArray.clear();
+        SparseBooleanArray selected = mPhotoSetView.getCheckedItemPositions();
+
+        for (int i = 0; i < selected.size(); i++) {
+            if (selected.valueAt(i)) {
+                Cursor item = mAdapter.getItem(selected.keyAt(i));
+                int supported = item.getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS);
+                if ((supported & MediaItem.SUPPORT_SHARE) > 0) {
+                    mSelectedUrisArray.add(mLoaderCompatShim.uriForItem(item));
+                }
+            }
+        }
+
+        return mSelectedUrisArray;
+    }
+
     @Override
     public void onLoaderReset(Loader<Cursor> loader) {
     }
 
-    private static class ThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter {
-        private LayoutInflater mInflater;
-        private LoaderCompatShim<Cursor> mDrawableFactory;
 
-        public ThumbnailAdapter(Context context) {
-            super(context, null, false);
-            mInflater = LayoutInflater.from(context);
-        }
+    private ShareActionProvider mShareActionProvider;
+    private ActionMode mActionMode;
+    private boolean mSharePending = false;
 
-        public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
-            mDrawableFactory = factory;
-        }
+    private void updateSelectedTitle(ActionMode mode) {
+        int count = mPhotoSetView.getCheckedItemCount();
+        mode.setTitle(getResources().getQuantityString(
+                R.plurals.number_of_items_selected, count, count));
+    }
 
-        @Override
-        public void bindView(View view, Context context, Cursor cursor) {
-            ImageView iv = (ImageView) view;
-            Drawable recycle = iv.getDrawable();
-            Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
-            if (recycle != drawable) {
-                iv.setImageDrawable(drawable);
-            }
-        }
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+            boolean checked) {
+        updateSelectedTitle(mode);
+        Cursor item = mAdapter.getItem(position);
 
-        @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            View view = mInflater.inflate(R.layout.photo_set_item, parent, false);
-            LayoutParams params = view.getLayoutParams();
-            int columnWidth = ((GridView) parent).getColumnWidth();
-            params.height = columnWidth;
-            view.setLayoutParams(params);
-            return view;
+        if (checked) {
+            mSelectedUris.add(mLoaderCompatShim.uriForItem(item));
+        } else {
+            mSelectedUris.remove(mLoaderCompatShim.uriForItem(item));
         }
 
-        @Override
-        public float getIntrinsicAspectRatio(int position) {
-            Cursor cursor = getItem(position);
-            float width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
-            float height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
-            return width / height;
-        }
+        mSelectionManager.onItemSelectedStateChanged(mShareActionProvider,
+                item.getInt(PhotoSetLoader.INDEX_MEDIA_TYPE),
+                item.getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS),
+                checked);
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        mSelectionManager.setSelectedUriSource(PhotoSetFragment.this);
+        mActionMode = mode;
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.gallery_multiselect, menu);
+        MenuItem menuItem = menu.findItem(R.id.menu_share);
+        mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
+        mShareActionProvider.setOnShareTargetSelectedListener(this);
+        updateSelectedTitle(mode);
+        return true;
+    }
 
-        @Override
-        public Cursor getItem(int position) {
-            return (Cursor) super.getItem(position);
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        mSelectedUris.clear();
+        if (mSharePending) {
+            // onDestroyActionMode gets called when the share target was selected,
+            // but apparently before the ArrayList is serialized in the intent
+            // so we can't clear the old one here.
+            mSelectedUrisArray = new ArrayList<Uri>();
+            mSharePending = false;
+        } else {
+            mSelectedUrisArray.clear();
         }
+        mSelectionManager.onClearSelection();
+        mSelectionManager.setSelectedUriSource(null);
+        mShareActionProvider = null;
+        mActionMode = null;
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        updateSelectedTitle(mode);
+        return false;
+    }
+
+    @Override
+    public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) {
+        mSharePending = true;
+        mActionMode.finish();
+        return false;
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        return false;
     }
 }
diff --git a/src/com/android/photos/SelectionManager.java b/src/com/android/photos/SelectionManager.java
new file mode 100644 (file)
index 0000000..d7e61d1
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcEvent;
+import android.provider.MediaStore.Files.FileColumns;
+import android.widget.ShareActionProvider;
+
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.GalleryUtils;
+
+import java.util.ArrayList;
+
+public class SelectionManager implements NfcAdapter.CreateBeamUrisCallback {
+    private Activity mActivity;
+    private NfcAdapter mNfcAdapter;
+    private SelectedUriSource mUriSource;
+    private Intent mShareIntent = new Intent();
+
+    public interface SelectedUriSource {
+        public ArrayList<Uri> getSelectedShareableUris();
+    }
+
+    @TargetApi(16)
+    public SelectionManager(Activity activity) {
+        mActivity = activity;
+        if (ApiHelper.AT_LEAST_16) {
+            mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity);
+            mNfcAdapter.setBeamPushUrisCallback(this, mActivity);
+        }
+    }
+
+    public void setSelectedUriSource(SelectedUriSource source) {
+        mUriSource = source;
+    }
+
+    private int mSelectedTotalCount = 0;
+    private int mSelectedShareableCount = 0;
+    private int mSelectedShareableImageCount = 0;
+    private int mSelectedShareableVideoCount = 0;
+    private int mSelectedDeletableCount = 0;
+
+    private ArrayList<Uri> mCachedShareableUris = null;
+
+    public void onItemSelectedStateChanged(ShareActionProvider share,
+            int itemType, int itemSupportedOperations, boolean selected) {
+        int increment = selected ? 1 : -1;
+
+        mSelectedTotalCount += increment;
+        mCachedShareableUris = null;
+
+        if ((itemSupportedOperations & MediaItem.SUPPORT_DELETE) > 0) {
+            mSelectedDeletableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaItem.SUPPORT_SHARE) > 0) {
+            mSelectedShareableCount += increment;
+            if (itemType == FileColumns.MEDIA_TYPE_IMAGE) {
+                mSelectedShareableImageCount += increment;
+            } else if (itemType == FileColumns.MEDIA_TYPE_VIDEO) {
+                mSelectedShareableVideoCount += increment;
+            }
+        }
+
+        mShareIntent.removeExtra(Intent.EXTRA_STREAM);
+        if (mSelectedShareableCount == 0) {
+            mShareIntent.setAction(null).setType(null);
+        } else if (mSelectedShareableCount >= 1) {
+            mCachedShareableUris = mUriSource.getSelectedShareableUris();
+            if (mSelectedShareableImageCount == mSelectedShareableCount) {
+                mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE);
+            } else if (mSelectedShareableVideoCount == mSelectedShareableCount) {
+                mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO);
+            } else {
+                mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL);
+            }
+            if (mSelectedShareableCount == 1) {
+                mShareIntent.setAction(Intent.ACTION_SEND);
+                mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0));
+            } else {
+                mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
+                mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris);
+            }
+        }
+        share.setShareIntent(mShareIntent);
+
+        // TODO update deletability, editability, etc.
+    }
+
+    public void onClearSelection() {
+        mSelectedTotalCount = 0;
+        mSelectedShareableCount = 0;
+        mSelectedShareableImageCount = 0;
+        mSelectedShareableVideoCount = 0;
+        mSelectedDeletableCount = 0;
+        mCachedShareableUris = null;
+        mShareIntent.removeExtra(Intent.EXTRA_STREAM);
+        mShareIntent.setAction(null).setType(null);
+    }
+
+    @Override
+    public Uri[] createBeamUris(NfcEvent event) {
+        // This will have been preceded by a call to onItemSelectedStateChange
+        if (mCachedShareableUris == null) return null;
+        return mCachedShareableUris.toArray(new Uri[mCachedShareableUris.size()]);
+    }
+}
diff --git a/src/com/android/photos/adapters/PhotoThumbnailAdapter.java b/src/com/android/photos/adapters/PhotoThumbnailAdapter.java
new file mode 100644 (file)
index 0000000..5715795
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.photos.adapters;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.CursorAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
+
+
+public class PhotoThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter {
+    private LayoutInflater mInflater;
+    private LoaderCompatShim<Cursor> mDrawableFactory;
+
+    public PhotoThumbnailAdapter(Context context) {
+        super(context, null, false);
+        mInflater = LayoutInflater.from(context);
+    }
+
+    public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
+        mDrawableFactory = factory;
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        ImageView iv = (ImageView) view;
+        Drawable recycle = iv.getDrawable();
+        Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+        if (recycle != drawable) {
+            iv.setImageDrawable(drawable);
+        }
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        View view = mInflater.inflate(R.layout.photo_set_item, parent, false);
+        LayoutParams params = view.getLayoutParams();
+        int columnWidth = ((GridView) parent).getColumnWidth();
+        params.height = columnWidth;
+        view.setLayoutParams(params);
+        return view;
+    }
+
+    @Override
+    public float getIntrinsicAspectRatio(int position) {
+        Cursor cursor = getItem(position);
+        float width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
+        float height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
+        return width / height;
+    }
+
+    @Override
+    public Cursor getItem(int position) {
+        return (Cursor) super.getItem(position);
+    }
+}
\ No newline at end of file
index 78662cd..72c8e93 100644 (file)
@@ -31,6 +31,8 @@ import com.android.photos.shims.LoaderCompatShim;
 
 public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> {
 
+    public static final String SUPPORTED_OPERATIONS = "supported_operations";
+
     private static final Uri CONTENT_URI = Files.getContentUri("external");
     public static final String[] PROJECTION = new String[] {
         FileColumns._ID,
@@ -39,7 +41,9 @@ public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cur
         FileColumns.HEIGHT,
         FileColumns.DATE_ADDED,
         FileColumns.MEDIA_TYPE,
+        SUPPORTED_OPERATIONS,
     };
+
     private static final String SORT_ORDER = FileColumns.DATE_ADDED + " DESC";
     private static final String SELECTION =
             FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_IMAGE
@@ -52,6 +56,7 @@ public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cur
     public static final int INDEX_HEIGHT = 3;
     public static final int INDEX_DATE_ADDED = 4;
     public static final int INDEX_MEDIA_TYPE = 5;
+    public static final int INDEX_SUPPORTED_OPERATIONS = 6;
 
     private static final Uri GLOBAL_CONTENT_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/external/");
     private final ContentObserver mGlobalObserver = new ForceLoadContentObserver();
index 9d3c7e9..32dbc80 100644 (file)
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.photos.shims;
 
 import android.graphics.Bitmap;
index 9c270a5..fa41c8e 100644 (file)
@@ -121,6 +121,8 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC
                     mappedMediaType = FileColumns.MEDIA_TYPE_VIDEO;
                 }
                 row[PhotoSetLoader.INDEX_MEDIA_TYPE] = mappedMediaType;
+                row[PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS] =
+                        item.getSupportedOperations();
                 cursor.addRow(row);
                 mediaItems.append(index, item);
             }