Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.gallery3d.ui; |
| 18 | |
Ray Chen | 875423f | 2011-10-05 14:32:58 +0800 | [diff] [blame] | 19 | import android.app.Activity; |
| 20 | import android.content.Context; |
| 21 | import android.content.Intent; |
| 22 | import android.net.Uri; |
| 23 | import android.os.Handler; |
| 24 | import android.view.ActionMode; |
| 25 | import android.view.LayoutInflater; |
| 26 | import android.view.Menu; |
| 27 | import android.view.MenuInflater; |
| 28 | import android.view.MenuItem; |
| 29 | import android.view.View; |
| 30 | import android.widget.Button; |
Ray Chen | 875423f | 2011-10-05 14:32:58 +0800 | [diff] [blame] | 31 | import android.widget.PopupMenu.OnMenuItemClickListener; |
Owen Lin | 73a04ff | 2012-03-14 17:27:24 +0800 | [diff] [blame] | 32 | import android.widget.ShareActionProvider; |
Ray Chen | 875423f | 2011-10-05 14:32:58 +0800 | [diff] [blame] | 33 | import android.widget.ShareActionProvider.OnShareTargetSelectedListener; |
| 34 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 35 | import com.android.gallery3d.R; |
| 36 | import com.android.gallery3d.app.GalleryActionBar; |
| 37 | import com.android.gallery3d.app.GalleryActivity; |
| 38 | import com.android.gallery3d.common.Utils; |
| 39 | import com.android.gallery3d.data.DataManager; |
| 40 | import com.android.gallery3d.data.MediaObject; |
| 41 | import com.android.gallery3d.data.Path; |
| 42 | import com.android.gallery3d.ui.CustomMenu.DropDownMenu; |
| 43 | import com.android.gallery3d.ui.MenuExecutor.ProgressListener; |
| 44 | import com.android.gallery3d.util.Future; |
| 45 | import com.android.gallery3d.util.GalleryUtils; |
| 46 | import com.android.gallery3d.util.ThreadPool.Job; |
| 47 | import com.android.gallery3d.util.ThreadPool.JobContext; |
| 48 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 49 | import java.util.ArrayList; |
| 50 | |
| 51 | public class ActionModeHandler implements ActionMode.Callback { |
| 52 | private static final String TAG = "ActionModeHandler"; |
| 53 | private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE |
| 54 | | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE |
| 55 | | MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT; |
| 56 | |
| 57 | public interface ActionModeListener { |
| 58 | public boolean onActionItemClicked(MenuItem item); |
| 59 | } |
| 60 | |
| 61 | private final GalleryActivity mActivity; |
| 62 | private final MenuExecutor mMenuExecutor; |
| 63 | private final SelectionManager mSelectionManager; |
| 64 | private Menu mMenu; |
| 65 | private DropDownMenu mSelectionMenu; |
| 66 | private ActionModeListener mListener; |
| 67 | private Future<?> mMenuTask; |
Ray Chen | 875423f | 2011-10-05 14:32:58 +0800 | [diff] [blame] | 68 | private final Handler mMainHandler; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 69 | private ShareActionProvider mShareActionProvider; |
| 70 | |
| 71 | public ActionModeHandler( |
| 72 | GalleryActivity activity, SelectionManager selectionManager) { |
| 73 | mActivity = Utils.checkNotNull(activity); |
| 74 | mSelectionManager = Utils.checkNotNull(selectionManager); |
| 75 | mMenuExecutor = new MenuExecutor(activity, selectionManager); |
| 76 | mMainHandler = new Handler(activity.getMainLooper()); |
| 77 | } |
| 78 | |
| 79 | public ActionMode startActionMode() { |
| 80 | Activity a = (Activity) mActivity; |
| 81 | final ActionMode actionMode = a.startActionMode(this); |
| 82 | CustomMenu customMenu = new CustomMenu(a); |
| 83 | View customView = LayoutInflater.from(a).inflate( |
| 84 | R.layout.action_mode, null); |
| 85 | actionMode.setCustomView(customView); |
| 86 | mSelectionMenu = customMenu.addDropDownMenu( |
| 87 | (Button) customView.findViewById(R.id.selection_menu), |
| 88 | R.menu.selection); |
Ray Chen | afa492c | 2011-09-19 12:35:51 +0800 | [diff] [blame] | 89 | updateSelectionMenu(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 90 | customMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| 91 | public boolean onMenuItemClick(MenuItem item) { |
| 92 | return onActionItemClicked(actionMode, item); |
| 93 | } |
| 94 | }); |
| 95 | return actionMode; |
| 96 | } |
| 97 | |
| 98 | public void setTitle(String title) { |
| 99 | mSelectionMenu.setTitle(title); |
| 100 | } |
| 101 | |
| 102 | public void setActionModeListener(ActionModeListener listener) { |
| 103 | mListener = listener; |
| 104 | } |
| 105 | |
| 106 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| 107 | boolean result; |
| 108 | if (mListener != null) { |
| 109 | result = mListener.onActionItemClicked(item); |
| 110 | if (result) { |
| 111 | mSelectionManager.leaveSelectionMode(); |
| 112 | return result; |
| 113 | } |
| 114 | } |
| 115 | ProgressListener listener = null; |
| 116 | if (item.getItemId() == R.id.action_import) { |
| 117 | listener = new ImportCompleteListener(mActivity); |
| 118 | } |
| 119 | result = mMenuExecutor.onMenuClicked(item, listener); |
| 120 | if (item.getItemId() == R.id.action_select_all) { |
| 121 | updateSupportedOperation(); |
Ray Chen | afa492c | 2011-09-19 12:35:51 +0800 | [diff] [blame] | 122 | updateSelectionMenu(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 123 | } |
| 124 | return result; |
| 125 | } |
| 126 | |
Ray Chen | afa492c | 2011-09-19 12:35:51 +0800 | [diff] [blame] | 127 | private void updateSelectionMenu() { |
| 128 | // update title |
| 129 | int count = mSelectionManager.getSelectedCount(); |
| 130 | String format = mActivity.getResources().getQuantityString( |
| 131 | R.plurals.number_of_items_selected, count); |
| 132 | setTitle(String.format(format, count)); |
Ray Chen | df483f4 | 2011-09-10 15:29:51 +0800 | [diff] [blame] | 133 | // For clients who call SelectionManager.selectAll() directly, we need to ensure the |
| 134 | // menu status is consistent with selection manager. |
| 135 | MenuItem item = mSelectionMenu.findItem(R.id.action_select_all); |
| 136 | if (item != null) { |
| 137 | if (mSelectionManager.inSelectAllMode()) { |
| 138 | item.setChecked(true); |
| 139 | item.setTitle(R.string.deselect_all); |
| 140 | } else { |
| 141 | item.setChecked(false); |
| 142 | item.setTitle(R.string.select_all); |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 147 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| 148 | MenuInflater inflater = mode.getMenuInflater(); |
| 149 | inflater.inflate(R.menu.operation, menu); |
| 150 | |
| 151 | mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu); |
Ray Chen | d2e19e1 | 2011-08-24 15:18:31 +0800 | [diff] [blame] | 152 | OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() { |
| 153 | public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) { |
| 154 | mSelectionManager.leaveSelectionMode(); |
| 155 | return false; |
| 156 | } |
| 157 | }; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 158 | |
Ray Chen | d2e19e1 | 2011-08-24 15:18:31 +0800 | [diff] [blame] | 159 | mShareActionProvider.setOnShareTargetSelectedListener(listener); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 160 | mMenu = menu; |
| 161 | return true; |
| 162 | } |
| 163 | |
| 164 | public void onDestroyActionMode(ActionMode mode) { |
| 165 | mSelectionManager.leaveSelectionMode(); |
| 166 | } |
| 167 | |
| 168 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| 169 | return true; |
| 170 | } |
| 171 | |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 172 | // Menu options are determined by selection set itself. |
| 173 | // We cannot expand it because MenuExecuter executes it based on |
| 174 | // the selection set instead of the expanded result. |
| 175 | // e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't. |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 176 | private int computeMenuOptions(JobContext jc) { |
| 177 | ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false); |
| 178 | if (unexpandedPaths.isEmpty()) { |
| 179 | // This happens when starting selection mode from overflow menu |
| 180 | // (instead of long press a media object) |
| 181 | return 0; |
| 182 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 183 | int operation = MediaObject.SUPPORT_ALL; |
| 184 | DataManager manager = mActivity.getDataManager(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 185 | int type = 0; |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 186 | for (Path path : unexpandedPaths) { |
| 187 | if (jc.isCancelled()) return 0; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 188 | int support = manager.getSupportedOperations(path); |
| 189 | type |= manager.getMediaType(path); |
| 190 | operation &= support; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 191 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 192 | |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 193 | switch (unexpandedPaths.size()) { |
| 194 | case 1: |
| 195 | final String mimeType = MenuExecutor.getMimeType(type); |
| 196 | if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) { |
| 197 | operation &= ~MediaObject.SUPPORT_EDIT; |
| 198 | } |
| 199 | break; |
| 200 | default: |
| 201 | operation &= SUPPORT_MULTIPLE_MASK; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 202 | } |
| 203 | |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 204 | return operation; |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 205 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 206 | |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 207 | // Share intent needs to expand the selection set so we can get URI of |
| 208 | // each media item |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 209 | private Intent computeSharingIntent(JobContext jc) { |
| 210 | ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true); |
| 211 | if (expandedPaths.size() == 0) return null; |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 212 | final ArrayList<Uri> uris = new ArrayList<Uri>(); |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 213 | DataManager manager = mActivity.getDataManager(); |
| 214 | int type = 0; |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 215 | final Intent intent = new Intent(); |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 216 | for (Path path : expandedPaths) { |
| 217 | if (jc.isCancelled()) return null; |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 218 | int support = manager.getSupportedOperations(path); |
| 219 | type |= manager.getMediaType(path); |
| 220 | |
| 221 | if ((support & MediaObject.SUPPORT_SHARE) != 0) { |
| 222 | uris.add(manager.getContentUri(path)); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | final int size = uris.size(); |
| 227 | if (size > 0) { |
| 228 | final String mimeType = MenuExecutor.getMimeType(type); |
| 229 | if (size > 1) { |
| 230 | intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType); |
| 231 | intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); |
| 232 | } else { |
| 233 | intent.setAction(Intent.ACTION_SEND).setType(mimeType); |
| 234 | intent.putExtra(Intent.EXTRA_STREAM, uris.get(0)); |
| 235 | } |
| 236 | intent.setType(mimeType); |
Ray Chen | 571c9e1 | 2011-09-14 15:12:29 +0800 | [diff] [blame] | 237 | } |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 238 | |
| 239 | return intent; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 240 | } |
| 241 | |
| 242 | public void updateSupportedOperation(Path path, boolean selected) { |
| 243 | // TODO: We need to improve the performance |
| 244 | updateSupportedOperation(); |
| 245 | } |
| 246 | |
| 247 | public void updateSupportedOperation() { |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 248 | // Interrupt previous unfinished task, mMenuTask is only accessed in main thread |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 249 | if (mMenuTask != null) { |
| 250 | mMenuTask.cancel(); |
| 251 | } |
| 252 | |
| 253 | // Disable share action until share intent is in good shape |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 254 | final MenuItem item = mShareActionProvider != null ? |
| 255 | mMenu.findItem(R.id.action_share) : null; |
| 256 | final boolean supportShare = item != null; |
| 257 | if (supportShare) item.setEnabled(false); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 258 | |
| 259 | // Generate sharing intent and update supported operations in the background |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 260 | // The task can take a long time and be canceled in the mean time. |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 261 | mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() { |
Ray Chen | 60bcd2f | 2012-02-20 13:32:47 +0800 | [diff] [blame] | 262 | public Void run(final JobContext jc) { |
| 263 | // Pass1: Deal with unexpanded media object list for menu operation. |
| 264 | final int operation = computeMenuOptions(jc); |
| 265 | |
| 266 | // Pass2: Deal with expanded media object list for sharing operation. |
| 267 | final Intent intent = supportShare ? computeSharingIntent(jc) : null; |
| 268 | mMainHandler.post(new Runnable() { |
| 269 | public void run() { |
| 270 | mMenuTask = null; |
| 271 | if (!jc.isCancelled()) { |
| 272 | MenuExecutor.updateMenuOperation(mMenu, operation); |
| 273 | if (supportShare) { |
| 274 | item.setEnabled(true); |
| 275 | mShareActionProvider.setShareIntent(intent); |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | }); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 280 | return null; |
| 281 | } |
| 282 | }); |
| 283 | } |
| 284 | |
| 285 | public void pause() { |
| 286 | if (mMenuTask != null) { |
| 287 | mMenuTask.cancel(); |
| 288 | mMenuTask = null; |
| 289 | } |
| 290 | mMenuExecutor.pause(); |
| 291 | } |
| 292 | |
| 293 | public void resume() { |
Ray Chen | 875423f | 2011-10-05 14:32:58 +0800 | [diff] [blame] | 294 | if (mSelectionManager.inSelectionMode()) updateSupportedOperation(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 295 | } |
| 296 | } |