blob: b46a39307de5ba30890c017be7c56a21abb62809 [file] [log] [blame]
/*
* 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.app.AlertDialog;
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.Toolbar;
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.MediaSet;
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);
}
public static boolean isThreadComplete = false;
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 SelectionMenu mSelectionMenu;
private ActionModeListener mListener;
private Future<?> mMenuTask;
private final Handler mMainHandler;
private ActionMode mActionMode;
private boolean mShareMaxDialog = false;
private Toolbar mToolbar;
private Intent mShareIntent;
private Intent mSharePanoramaIntent;
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());
mToolbar = mActivity.getToolbar();
}
public void startActionMode() {
mMenuExecutor.setLeaving(false);
Activity a = mActivity;
// mActionMode = a.startActionMode(this);
mActionMode = mActivity.getToolbar().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;
} else if (action == R.id.action_share) {
String shareTitle = mActivity.getResources().
getString(R.string.share_dialogue_title);
mActivity.startActivity(Intent.createChooser(
mShareIntent, shareTitle));
return true;
}
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);
mSelectionMenu.setLeaving(mMenuExecutor.isLeaving());
}
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));
if (count == 0) {
mShareMaxDialog = false;
}
// For clients who call SelectionManager.selectAll() directly, we need to ensure the
// menu status is consistent with selection manager.
mSelectionMenu.updateSelectAllMode(mSelectionManager.inSelectAllMode());
}
@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);
mActivity.getToolbar().setVisibility(View.INVISIBLE);
mMenu = menu;
mSharePanoramaMenuItem = menu.findItem(R.id.action_share_panorama);
if (mSharePanoramaMenuItem != null) {
mSharePanoramaMenuItem.setOnMenuItemClickListener(
new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mSelectionManager.leaveSelectionMode();
if (mSharePanoramaIntent != null) {
Intent intent = Intent.createChooser(mSharePanoramaIntent, null);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivity.startActivity(intent);
}
return true;
}
});
}
mShareMenuItem = menu.findItem(R.id.action_share);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mSelectionManager.leaveSelectionMode();
mActivity.getToolbar().setVisibility(View.VISIBLE);
}
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() || !mSelectionManager.inSelectionMode()) {
return null;
}
MediaObject mediaObject = manager.getMediaObject(path);
if (mediaObject != null && mediaObject.isSelectable()) {
selected.add(mediaObject);
}
}
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 = computeMenuOptionsMultiple(operation, selected.size());
}
return operation;
}
private int computeMenuOptionsMultiple(int operationBefore, int selectedSize) {
int operation = operationBefore;
if (selectedSize > 1) {
operation &= SUPPORT_MULTIPLE_MASK;
}
return operation;
}
private boolean computeCanShare(ArrayList<MediaObject> selected, int max) {
int numSelected = selected.size();
boolean ret = computeCanShare(numSelected, max);
if (!ret) return false;
numSelected = 0;
for (MediaObject mediaObject : selected) {
if (mediaObject instanceof MediaSet) {
numSelected = numSelected + ((MediaSet) mediaObject).getTotalMediaItemCount();
} else {
numSelected = numSelected + 1;
}
ret = computeCanShare(numSelected, max);
if (!ret) return false;
}
return true;
}
private boolean computeCanShare(int size, int max) {
return size <= max;
}
@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;
Uri uri = manager.getContentUri(path);
if (uri != null) {
uris.add(uri);
}
}
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) {
Uri uri = manager.getContentUri(path);
if (uri != null) {
uris.add(uri);
}
}
}
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);
isThreadComplete = 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) {
// use selected count to disable menu options and share items if need
// to enhance performance.
int multipleOperation = SUPPORT_MULTIPLE_MASK;
int count = mSelectionManager.getSelectedCount();
final int operationPre = computeMenuOptionsMultiple(multipleOperation, count);
final boolean canSharePanoramasPre = computeCanShare(count,
MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT);
final boolean canSharePre = computeCanShare(count,
MAX_SELECTED_ITEMS_FOR_SHARE_INTENT);
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (jc.isCancelled()) return;
MenuExecutor.updateMenuOperation(mMenu, operationPre);
if (mShareMenuItem != null && !canSharePre) {
mShareMenuItem.setEnabled(false);
showShareMaxDialogIfNeed(false);
}
}
});
// 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;
}
// use selected items to compute menu options and share items again.
final boolean canShare = canSharePre
&& computeCanShare(selected, MAX_SELECTED_ITEMS_FOR_SHARE_INTENT);
final boolean canSharePanoramas = canSharePanoramasPre
&& computeCanShare(selected, MAX_SELECTED_ITEMS_FOR_PANORAMA_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));
}
mSharePanoramaIntent = share_panorama_intent;
}
if (mShareMenuItem != null) {
showShareMaxDialogIfNeed(canShare);
mShareMenuItem.setEnabled(canShare);
isThreadComplete = true;
mShareIntent = share_intent;
}
}
});
return null;
}
});
}
private void showShareMaxDialogIfNeed(boolean canShare) {
if (!canShare && !mShareMaxDialog) {
AlertDialog.Builder shareMaxDialog = new AlertDialog.Builder(mActivity);
shareMaxDialog
.setMessage(R.string.cannot_share_items)
.setPositiveButton(R.string.ok, null)
.create();
shareMaxDialog.show();
mShareMaxDialog = true;
}
if (canShare && mShareMaxDialog) {
mShareMaxDialog = false;
}
}
public void pause() {
if (mMenuTask != null) {
mMenuTask.cancel();
mMenuTask = null;
}
mMenuExecutor.pause();
}
public void destroy() {
mMenuExecutor.destroy();
}
public void resume() {
if (mSelectionManager.inSelectionMode()) {
mActivity.getToolbar().setVisibility(View.INVISIBLE);
updateSupportedOperation();
}
mMenuExecutor.resume();
}
}