summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/ActionModeHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/ActionModeHandler.java')
-rw-r--r--src/com/android/gallery3d/ui/ActionModeHandler.java501
1 files changed, 501 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java
new file mode 100644
index 000000000..6b4f10312
--- /dev/null
+++ b/src/com/android/gallery3d/ui/ActionModeHandler.java
@@ -0,0 +1,501 @@
+/*
+ * 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.ui;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.nfc.NfcAdapter;
+import android.os.Handler;
+import android.view.ActionMode;
+import android.view.ActionMode.Callback;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ShareActionProvider;
+import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.AbstractGalleryActivity;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.MenuExecutor.ProgressListener;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.util.ArrayList;
+
+public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickListener {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "ActionModeHandler";
+
+ private static final int MAX_SELECTED_ITEMS_FOR_SHARE_INTENT = 300;
+ private static final int MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT = 10;
+
+ private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE
+ | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE
+ | MediaObject.SUPPORT_CACHE;
+
+ public interface ActionModeListener {
+ public boolean onActionItemClicked(MenuItem item);
+ }
+
+ private final AbstractGalleryActivity mActivity;
+ private final MenuExecutor mMenuExecutor;
+ private final SelectionManager mSelectionManager;
+ private final NfcAdapter mNfcAdapter;
+ private Menu mMenu;
+ private MenuItem mSharePanoramaMenuItem;
+ private MenuItem mShareMenuItem;
+ private ShareActionProvider mSharePanoramaActionProvider;
+ private ShareActionProvider mShareActionProvider;
+ private SelectionMenu mSelectionMenu;
+ private ActionModeListener mListener;
+ private Future<?> mMenuTask;
+ private final Handler mMainHandler;
+ private ActionMode mActionMode;
+
+ private static class GetAllPanoramaSupports implements PanoramaSupportCallback {
+ private int mNumInfoRequired;
+ private JobContext mJobContext;
+ public boolean mAllPanoramas = true;
+ public boolean mAllPanorama360 = true;
+ public boolean mHasPanorama360 = false;
+ private Object mLock = new Object();
+
+ public GetAllPanoramaSupports(ArrayList<MediaObject> mediaObjects, JobContext jc) {
+ mJobContext = jc;
+ mNumInfoRequired = mediaObjects.size();
+ for (MediaObject mediaObject : mediaObjects) {
+ mediaObject.getPanoramaSupport(this);
+ }
+ }
+
+ @Override
+ public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
+ boolean isPanorama360) {
+ synchronized (mLock) {
+ mNumInfoRequired--;
+ mAllPanoramas = isPanorama && mAllPanoramas;
+ mAllPanorama360 = isPanorama360 && mAllPanorama360;
+ mHasPanorama360 = mHasPanorama360 || isPanorama360;
+ if (mNumInfoRequired == 0 || mJobContext.isCancelled()) {
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ public void waitForPanoramaSupport() {
+ synchronized (mLock) {
+ while (mNumInfoRequired != 0 && !mJobContext.isCancelled()) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // May be a cancelled job context
+ }
+ }
+ }
+ }
+ }
+
+ public ActionModeHandler(
+ AbstractGalleryActivity activity, SelectionManager selectionManager) {
+ mActivity = Utils.checkNotNull(activity);
+ mSelectionManager = Utils.checkNotNull(selectionManager);
+ mMenuExecutor = new MenuExecutor(activity, selectionManager);
+ mMainHandler = new Handler(activity.getMainLooper());
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
+ }
+
+ public void startActionMode() {
+ Activity a = mActivity;
+ mActionMode = a.startActionMode(this);
+ View customView = LayoutInflater.from(a).inflate(
+ R.layout.action_mode, null);
+ mActionMode.setCustomView(customView);
+ mSelectionMenu = new SelectionMenu(a,
+ (Button) customView.findViewById(R.id.selection_menu), this);
+ updateSelectionMenu();
+ }
+
+ public void finishActionMode() {
+ mActionMode.finish();
+ }
+
+ public void setTitle(String title) {
+ mSelectionMenu.setTitle(title);
+ }
+
+ public void setActionModeListener(ActionModeListener listener) {
+ mListener = listener;
+ }
+
+ private WakeLockHoldingProgressListener mDeleteProgressListener;
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ GLRoot root = mActivity.getGLRoot();
+ root.lockRenderThread();
+ try {
+ boolean result;
+ // Give listener a chance to process this command before it's routed to
+ // ActionModeHandler, which handles command only based on the action id.
+ // Sometimes the listener may have more background information to handle
+ // an action command.
+ if (mListener != null) {
+ result = mListener.onActionItemClicked(item);
+ if (result) {
+ mSelectionManager.leaveSelectionMode();
+ return result;
+ }
+ }
+ ProgressListener listener = null;
+ String confirmMsg = null;
+ int action = item.getItemId();
+ if (action == R.id.action_delete) {
+ confirmMsg = mActivity.getResources().getQuantityString(
+ R.plurals.delete_selection, mSelectionManager.getSelectedCount());
+ if (mDeleteProgressListener == null) {
+ mDeleteProgressListener = new WakeLockHoldingProgressListener(mActivity,
+ "Gallery Delete Progress Listener");
+ }
+ listener = mDeleteProgressListener;
+ }
+ mMenuExecutor.onMenuClicked(item, confirmMsg, listener);
+ } finally {
+ root.unlockRenderThread();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPopupItemClick(int itemId) {
+ GLRoot root = mActivity.getGLRoot();
+ root.lockRenderThread();
+ try {
+ if (itemId == R.id.action_select_all) {
+ updateSupportedOperation();
+ mMenuExecutor.onMenuClicked(itemId, null, false, true);
+ }
+ return true;
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ private void updateSelectionMenu() {
+ // update title
+ int count = mSelectionManager.getSelectedCount();
+ String format = mActivity.getResources().getQuantityString(
+ R.plurals.number_of_items_selected, count);
+ setTitle(String.format(format, count));
+
+ // For clients who call SelectionManager.selectAll() directly, we need to ensure the
+ // menu status is consistent with selection manager.
+ mSelectionMenu.updateSelectAllMode(mSelectionManager.inSelectAllMode());
+ }
+
+ private final OnShareTargetSelectedListener mShareTargetSelectedListener =
+ new OnShareTargetSelectedListener() {
+ @Override
+ public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
+ mSelectionManager.leaveSelectionMode();
+ return false;
+ }
+ };
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mode.getMenuInflater().inflate(R.menu.operation, menu);
+
+ mMenu = menu;
+ mSharePanoramaMenuItem = menu.findItem(R.id.action_share_panorama);
+ if (mSharePanoramaMenuItem != null) {
+ mSharePanoramaActionProvider = (ShareActionProvider) mSharePanoramaMenuItem
+ .getActionProvider();
+ mSharePanoramaActionProvider.setOnShareTargetSelectedListener(
+ mShareTargetSelectedListener);
+ mSharePanoramaActionProvider.setShareHistoryFileName("panorama_share_history.xml");
+ }
+ mShareMenuItem = menu.findItem(R.id.action_share);
+ if (mShareMenuItem != null) {
+ mShareActionProvider = (ShareActionProvider) mShareMenuItem
+ .getActionProvider();
+ mShareActionProvider.setOnShareTargetSelectedListener(
+ mShareTargetSelectedListener);
+ mShareActionProvider.setShareHistoryFileName("share_history.xml");
+ }
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ mSelectionManager.leaveSelectionMode();
+ }
+
+ private ArrayList<MediaObject> getSelectedMediaObjects(JobContext jc) {
+ ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false);
+ if (unexpandedPaths.isEmpty()) {
+ // This happens when starting selection mode from overflow menu
+ // (instead of long press a media object)
+ return null;
+ }
+ ArrayList<MediaObject> selected = new ArrayList<MediaObject>();
+ DataManager manager = mActivity.getDataManager();
+ for (Path path : unexpandedPaths) {
+ if (jc.isCancelled()) {
+ return null;
+ }
+ selected.add(manager.getMediaObject(path));
+ }
+
+ return selected;
+ }
+ // Menu options are determined by selection set itself.
+ // We cannot expand it because MenuExecuter executes it based on
+ // the selection set instead of the expanded result.
+ // e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't.
+ private int computeMenuOptions(ArrayList<MediaObject> selected) {
+ int operation = MediaObject.SUPPORT_ALL;
+ int type = 0;
+ for (MediaObject mediaObject: selected) {
+ int support = mediaObject.getSupportedOperations();
+ type |= mediaObject.getMediaType();
+ operation &= support;
+ }
+
+ switch (selected.size()) {
+ case 1:
+ final String mimeType = MenuExecutor.getMimeType(type);
+ if (!GalleryUtils.isEditorAvailable(mActivity, mimeType)) {
+ operation &= ~MediaObject.SUPPORT_EDIT;
+ }
+ break;
+ default:
+ operation &= SUPPORT_MULTIPLE_MASK;
+ }
+
+ return operation;
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void setNfcBeamPushUris(Uri[] uris) {
+ if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
+ mNfcAdapter.setBeamPushUrisCallback(null, mActivity);
+ mNfcAdapter.setBeamPushUris(uris, mActivity);
+ }
+ }
+
+ // Share intent needs to expand the selection set so we can get URI of
+ // each media item
+ private Intent computePanoramaSharingIntent(JobContext jc, int maxItems) {
+ ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true, maxItems);
+ if (expandedPaths == null || expandedPaths.size() == 0) {
+ return new Intent();
+ }
+ final ArrayList<Uri> uris = new ArrayList<Uri>();
+ DataManager manager = mActivity.getDataManager();
+ final Intent intent = new Intent();
+ for (Path path : expandedPaths) {
+ if (jc.isCancelled()) return null;
+ uris.add(manager.getContentUri(path));
+ }
+
+ final int size = uris.size();
+ if (size > 0) {
+ if (size > 1) {
+ intent.setAction(Intent.ACTION_SEND_MULTIPLE);
+ intent.setType(GalleryUtils.MIME_TYPE_PANORAMA360);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+ } else {
+ intent.setAction(Intent.ACTION_SEND);
+ intent.setType(GalleryUtils.MIME_TYPE_PANORAMA360);
+ intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
+ }
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+
+ return intent;
+ }
+
+ private Intent computeSharingIntent(JobContext jc, int maxItems) {
+ ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true, maxItems);
+ if (expandedPaths == null || expandedPaths.size() == 0) {
+ setNfcBeamPushUris(null);
+ return new Intent();
+ }
+ final ArrayList<Uri> uris = new ArrayList<Uri>();
+ DataManager manager = mActivity.getDataManager();
+ int type = 0;
+ final Intent intent = new Intent();
+ for (Path path : expandedPaths) {
+ if (jc.isCancelled()) return null;
+ int support = manager.getSupportedOperations(path);
+ type |= manager.getMediaType(path);
+
+ if ((support & MediaObject.SUPPORT_SHARE) != 0) {
+ uris.add(manager.getContentUri(path));
+ }
+ }
+
+ final int size = uris.size();
+ if (size > 0) {
+ final String mimeType = MenuExecutor.getMimeType(type);
+ if (size > 1) {
+ intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+ } else {
+ intent.setAction(Intent.ACTION_SEND).setType(mimeType);
+ intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
+ }
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ setNfcBeamPushUris(uris.toArray(new Uri[uris.size()]));
+ } else {
+ setNfcBeamPushUris(null);
+ }
+
+ return intent;
+ }
+
+ public void updateSupportedOperation(Path path, boolean selected) {
+ // TODO: We need to improve the performance
+ updateSupportedOperation();
+ }
+
+ public void updateSupportedOperation() {
+ // Interrupt previous unfinished task, mMenuTask is only accessed in main thread
+ if (mMenuTask != null) mMenuTask.cancel();
+
+ updateSelectionMenu();
+
+ // Disable share actions until share intent is in good shape
+ if (mSharePanoramaMenuItem != null) mSharePanoramaMenuItem.setEnabled(false);
+ if (mShareMenuItem != null) mShareMenuItem.setEnabled(false);
+
+ // Generate sharing intent and update supported operations in the background
+ // The task can take a long time and be canceled in the mean time.
+ mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() {
+ @Override
+ public Void run(final JobContext jc) {
+ // Pass1: Deal with unexpanded media object list for menu operation.
+ ArrayList<MediaObject> selected = getSelectedMediaObjects(jc);
+ if (selected == null) {
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mMenuTask = null;
+ if (jc.isCancelled()) return;
+ // Disable all the operations when no item is selected
+ MenuExecutor.updateMenuOperation(mMenu, 0);
+ }
+ });
+ return null;
+ }
+ final int operation = computeMenuOptions(selected);
+ if (jc.isCancelled()) {
+ return null;
+ }
+ int numSelected = selected.size();
+ final boolean canSharePanoramas =
+ numSelected < MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT;
+ final boolean canShare =
+ numSelected < MAX_SELECTED_ITEMS_FOR_SHARE_INTENT;
+
+ final GetAllPanoramaSupports supportCallback = canSharePanoramas ?
+ new GetAllPanoramaSupports(selected, jc)
+ : null;
+
+ // Pass2: Deal with expanded media object list for sharing operation.
+ final Intent share_panorama_intent = canSharePanoramas ?
+ computePanoramaSharingIntent(jc, MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT)
+ : new Intent();
+ final Intent share_intent = canShare ?
+ computeSharingIntent(jc, MAX_SELECTED_ITEMS_FOR_SHARE_INTENT)
+ : new Intent();
+
+ if (canSharePanoramas) {
+ supportCallback.waitForPanoramaSupport();
+ }
+ if (jc.isCancelled()) {
+ return null;
+ }
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mMenuTask = null;
+ if (jc.isCancelled()) return;
+ MenuExecutor.updateMenuOperation(mMenu, operation);
+ MenuExecutor.updateMenuForPanorama(mMenu,
+ canSharePanoramas && supportCallback.mAllPanorama360,
+ canSharePanoramas && supportCallback.mHasPanorama360);
+ if (mSharePanoramaMenuItem != null) {
+ mSharePanoramaMenuItem.setEnabled(true);
+ if (canSharePanoramas && supportCallback.mAllPanorama360) {
+ mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ mShareMenuItem.setTitle(
+ mActivity.getResources().getString(R.string.share_as_photo));
+ } else {
+ mSharePanoramaMenuItem.setVisible(false);
+ mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ mShareMenuItem.setTitle(
+ mActivity.getResources().getString(R.string.share));
+ }
+ mSharePanoramaActionProvider.setShareIntent(share_panorama_intent);
+ }
+ if (mShareMenuItem != null) {
+ mShareMenuItem.setEnabled(canShare);
+ mShareActionProvider.setShareIntent(share_intent);
+ }
+ }
+ });
+ return null;
+ }
+ });
+ }
+
+ public void pause() {
+ if (mMenuTask != null) {
+ mMenuTask.cancel();
+ mMenuTask = null;
+ }
+ mMenuExecutor.pause();
+ }
+
+ public void destroy() {
+ mMenuExecutor.destroy();
+ }
+
+ public void resume() {
+ if (mSelectionManager.inSelectionMode()) updateSupportedOperation();
+ mMenuExecutor.resume();
+ }
+}