| /* |
| * 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.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.nfc.NfcAdapter; |
| import android.os.Handler; |
| 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.widget.Button; |
| import android.widget.PopupMenu.OnMenuItemClickListener; |
| import android.widget.ShareActionProvider; |
| import android.widget.ShareActionProvider.OnShareTargetSelectedListener; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.app.GalleryActionBar; |
| import com.android.gallery3d.app.GalleryActivity; |
| import com.android.gallery3d.common.Utils; |
| import com.android.gallery3d.data.DataManager; |
| import com.android.gallery3d.data.MediaObject; |
| import com.android.gallery3d.data.Path; |
| import com.android.gallery3d.ui.CustomMenu.DropDownMenu; |
| 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 ActionMode.Callback { |
| private static final String TAG = "ActionModeHandler"; |
| private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE |
| | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE |
| | MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT; |
| |
| public interface ActionModeListener { |
| public boolean onActionItemClicked(MenuItem item); |
| } |
| |
| private final GalleryActivity mActivity; |
| private final MenuExecutor mMenuExecutor; |
| private final SelectionManager mSelectionManager; |
| private final NfcAdapter mNfcAdapter; |
| private Menu mMenu; |
| private DropDownMenu mSelectionMenu; |
| private ActionModeListener mListener; |
| private Future<?> mMenuTask; |
| private final Handler mMainHandler; |
| private ShareActionProvider mShareActionProvider; |
| |
| public ActionModeHandler( |
| GalleryActivity 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 ActionMode startActionMode() { |
| Activity a = (Activity) mActivity; |
| final ActionMode actionMode = a.startActionMode(this); |
| CustomMenu customMenu = new CustomMenu(a); |
| View customView = LayoutInflater.from(a).inflate( |
| R.layout.action_mode, null); |
| actionMode.setCustomView(customView); |
| mSelectionMenu = customMenu.addDropDownMenu( |
| (Button) customView.findViewById(R.id.selection_menu), |
| R.menu.selection); |
| updateSelectionMenu(); |
| customMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| @Override |
| public boolean onMenuItemClick(MenuItem item) { |
| return onActionItemClicked(actionMode, item); |
| } |
| }); |
| return actionMode; |
| } |
| |
| public void setTitle(String title) { |
| mSelectionMenu.setTitle(title); |
| } |
| |
| public void setActionModeListener(ActionModeListener listener) { |
| mListener = listener; |
| } |
| |
| @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; |
| boolean needsConfirm = false; |
| int action = item.getItemId(); |
| if (action == R.id.action_import) { |
| listener = new ImportCompleteListener(mActivity); |
| } else if (item.getItemId() == R.id.action_delete) { |
| needsConfirm = true; |
| } |
| mMenuExecutor.onMenuClicked(item, needsConfirm, listener); |
| if (action == R.id.action_select_all) { |
| updateSupportedOperation(); |
| updateSelectionMenu(); |
| } |
| } finally { |
| root.unlockRenderThread(); |
| } |
| return true; |
| } |
| |
| 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. |
| MenuItem item = mSelectionMenu.findItem(R.id.action_select_all); |
| if (item != null) { |
| if (mSelectionManager.inSelectAllMode()) { |
| item.setChecked(true); |
| item.setTitle(R.string.deselect_all); |
| } else { |
| item.setChecked(false); |
| item.setTitle(R.string.select_all); |
| } |
| } |
| } |
| |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| MenuInflater inflater = mode.getMenuInflater(); |
| inflater.inflate(R.menu.operation, menu); |
| |
| mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu); |
| OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() { |
| public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) { |
| mSelectionManager.leaveSelectionMode(); |
| return false; |
| } |
| }; |
| |
| mShareActionProvider.setOnShareTargetSelectedListener(listener); |
| mMenu = menu; |
| return true; |
| } |
| |
| public void onDestroyActionMode(ActionMode mode) { |
| mSelectionManager.leaveSelectionMode(); |
| } |
| |
| public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| return true; |
| } |
| |
| // 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(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 0; |
| } |
| int operation = MediaObject.SUPPORT_ALL; |
| DataManager manager = mActivity.getDataManager(); |
| int type = 0; |
| for (Path path : unexpandedPaths) { |
| if (jc.isCancelled()) return 0; |
| int support = manager.getSupportedOperations(path); |
| type |= manager.getMediaType(path); |
| operation &= support; |
| } |
| |
| switch (unexpandedPaths.size()) { |
| case 1: |
| final String mimeType = MenuExecutor.getMimeType(type); |
| if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) { |
| operation &= ~MediaObject.SUPPORT_EDIT; |
| } |
| break; |
| default: |
| operation &= SUPPORT_MULTIPLE_MASK; |
| } |
| |
| return operation; |
| } |
| |
| // Share intent needs to expand the selection set so we can get URI of |
| // each media item |
| private Intent computeSharingIntent(JobContext jc) { |
| ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true); |
| if (expandedPaths.size() == 0) { |
| if (mNfcAdapter != null) { |
| mNfcAdapter.setBeamPushUris(null, (Activity)mActivity); |
| } |
| return null; |
| } |
| 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.setType(mimeType); |
| if (mNfcAdapter != null) { |
| mNfcAdapter.setBeamPushUris(uris.toArray(new Uri[uris.size()]), |
| (Activity)mActivity); |
| } |
| } else { |
| if (mNfcAdapter != null) { |
| mNfcAdapter.setBeamPushUris(null, (Activity)mActivity); |
| } |
| } |
| |
| 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(); |
| } |
| |
| // Disable share action until share intent is in good shape |
| final MenuItem item = mShareActionProvider != null ? |
| mMenu.findItem(R.id.action_share) : null; |
| final boolean supportShare = item != null; |
| if (supportShare) item.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>() { |
| public Void run(final JobContext jc) { |
| // Pass1: Deal with unexpanded media object list for menu operation. |
| final int operation = computeMenuOptions(jc); |
| |
| // Pass2: Deal with expanded media object list for sharing operation. |
| final Intent intent = supportShare ? computeSharingIntent(jc) : null; |
| mMainHandler.post(new Runnable() { |
| public void run() { |
| mMenuTask = null; |
| if (!jc.isCancelled()) { |
| MenuExecutor.updateMenuOperation(mMenu, operation); |
| if (supportShare) { |
| item.setEnabled(true); |
| mShareActionProvider.setShareIntent(intent); |
| } |
| } |
| } |
| }); |
| return null; |
| } |
| }); |
| } |
| |
| public void pause() { |
| if (mMenuTask != null) { |
| mMenuTask.cancel(); |
| mMenuTask = null; |
| } |
| mMenuExecutor.pause(); |
| } |
| |
| public void resume() { |
| if (mSelectionManager.inSelectionMode()) updateSupportedOperation(); |
| } |
| } |