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 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.nfc.NfcAdapter; 24 import android.os.Handler; 25 import android.view.ActionMode; 26 import android.view.LayoutInflater; 27 import android.view.Menu; 28 import android.view.MenuInflater; 29 import android.view.MenuItem; 30 import android.view.View; 31 import android.widget.Button; 32 import android.widget.PopupMenu.OnMenuItemClickListener; 33 import android.widget.ShareActionProvider; 34 import android.widget.ShareActionProvider.OnShareTargetSelectedListener; 35 36 import com.android.gallery3d.R; 37 import com.android.gallery3d.app.GalleryActionBar; 38 import com.android.gallery3d.app.GalleryActivity; 39 import com.android.gallery3d.common.Utils; 40 import com.android.gallery3d.data.DataManager; 41 import com.android.gallery3d.data.MediaObject; 42 import com.android.gallery3d.data.Path; 43 import com.android.gallery3d.ui.CustomMenu.DropDownMenu; 44 import com.android.gallery3d.ui.MenuExecutor.ProgressListener; 45 import com.android.gallery3d.util.Future; 46 import com.android.gallery3d.util.GalleryUtils; 47 import com.android.gallery3d.util.ThreadPool.Job; 48 import com.android.gallery3d.util.ThreadPool.JobContext; 49 50 import java.util.ArrayList; 51 52 public class ActionModeHandler implements ActionMode.Callback { 53 private static final String TAG = "ActionModeHandler"; 54 private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE 55 | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE 56 | MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT; 57 58 public interface ActionModeListener { onActionItemClicked(MenuItem item)59 public boolean onActionItemClicked(MenuItem item); 60 } 61 62 private final GalleryActivity mActivity; 63 private final MenuExecutor mMenuExecutor; 64 private final SelectionManager mSelectionManager; 65 private final NfcAdapter mNfcAdapter; 66 private Menu mMenu; 67 private DropDownMenu mSelectionMenu; 68 private ActionModeListener mListener; 69 private Future<?> mMenuTask; 70 private final Handler mMainHandler; 71 private ShareActionProvider mShareActionProvider; 72 ActionModeHandler( GalleryActivity activity, SelectionManager selectionManager)73 public ActionModeHandler( 74 GalleryActivity activity, SelectionManager selectionManager) { 75 mActivity = Utils.checkNotNull(activity); 76 mSelectionManager = Utils.checkNotNull(selectionManager); 77 mMenuExecutor = new MenuExecutor(activity, selectionManager); 78 mMainHandler = new Handler(activity.getMainLooper()); 79 mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext()); 80 } 81 startActionMode()82 public ActionMode startActionMode() { 83 Activity a = (Activity) mActivity; 84 final ActionMode actionMode = a.startActionMode(this); 85 CustomMenu customMenu = new CustomMenu(a); 86 View customView = LayoutInflater.from(a).inflate( 87 R.layout.action_mode, null); 88 actionMode.setCustomView(customView); 89 mSelectionMenu = customMenu.addDropDownMenu( 90 (Button) customView.findViewById(R.id.selection_menu), 91 R.menu.selection); 92 updateSelectionMenu(); 93 customMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { 94 @Override 95 public boolean onMenuItemClick(MenuItem item) { 96 return onActionItemClicked(actionMode, item); 97 } 98 }); 99 return actionMode; 100 } 101 setTitle(String title)102 public void setTitle(String title) { 103 mSelectionMenu.setTitle(title); 104 } 105 setActionModeListener(ActionModeListener listener)106 public void setActionModeListener(ActionModeListener listener) { 107 mListener = listener; 108 } 109 110 @Override onActionItemClicked(ActionMode mode, MenuItem item)111 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 112 GLRoot root = mActivity.getGLRoot(); 113 root.lockRenderThread(); 114 try { 115 boolean result; 116 // Give listener a chance to process this command before it's routed to 117 // ActionModeHandler, which handles command only based on the action id. 118 // Sometimes the listener may have more background information to handle 119 // an action command. 120 if (mListener != null) { 121 result = mListener.onActionItemClicked(item); 122 if (result) { 123 mSelectionManager.leaveSelectionMode(); 124 return result; 125 } 126 } 127 ProgressListener listener = null; 128 String confirmMsg = null; 129 int action = item.getItemId(); 130 if (action == R.id.action_import) { 131 listener = new ImportCompleteListener(mActivity); 132 } else if (item.getItemId() == R.id.action_delete) { 133 confirmMsg = mActivity.getResources().getQuantityString( 134 R.plurals.delete_selection, mSelectionManager.getSelectedCount()); 135 } 136 mMenuExecutor.onMenuClicked(item, confirmMsg, listener); 137 if (action == R.id.action_select_all) { 138 updateSupportedOperation(); 139 updateSelectionMenu(); 140 } 141 } finally { 142 root.unlockRenderThread(); 143 } 144 return true; 145 } 146 updateSelectionMenu()147 private void updateSelectionMenu() { 148 // update title 149 int count = mSelectionManager.getSelectedCount(); 150 String format = mActivity.getResources().getQuantityString( 151 R.plurals.number_of_items_selected, count); 152 setTitle(String.format(format, count)); 153 // For clients who call SelectionManager.selectAll() directly, we need to ensure the 154 // menu status is consistent with selection manager. 155 MenuItem item = mSelectionMenu.findItem(R.id.action_select_all); 156 if (item != null) { 157 if (mSelectionManager.inSelectAllMode()) { 158 item.setChecked(true); 159 item.setTitle(R.string.deselect_all); 160 } else { 161 item.setChecked(false); 162 item.setTitle(R.string.select_all); 163 } 164 } 165 } 166 onCreateActionMode(ActionMode mode, Menu menu)167 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 168 MenuInflater inflater = mode.getMenuInflater(); 169 inflater.inflate(R.menu.operation, menu); 170 171 mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu); 172 OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() { 173 public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) { 174 mSelectionManager.leaveSelectionMode(); 175 return false; 176 } 177 }; 178 179 mShareActionProvider.setOnShareTargetSelectedListener(listener); 180 mMenu = menu; 181 return true; 182 } 183 onDestroyActionMode(ActionMode mode)184 public void onDestroyActionMode(ActionMode mode) { 185 mSelectionManager.leaveSelectionMode(); 186 } 187 onPrepareActionMode(ActionMode mode, Menu menu)188 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 189 return true; 190 } 191 192 // Menu options are determined by selection set itself. 193 // We cannot expand it because MenuExecuter executes it based on 194 // the selection set instead of the expanded result. 195 // e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't. computeMenuOptions(JobContext jc)196 private int computeMenuOptions(JobContext jc) { 197 ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false); 198 if (unexpandedPaths.isEmpty()) { 199 // This happens when starting selection mode from overflow menu 200 // (instead of long press a media object) 201 return 0; 202 } 203 int operation = MediaObject.SUPPORT_ALL; 204 DataManager manager = mActivity.getDataManager(); 205 int type = 0; 206 for (Path path : unexpandedPaths) { 207 if (jc.isCancelled()) return 0; 208 int support = manager.getSupportedOperations(path); 209 type |= manager.getMediaType(path); 210 operation &= support; 211 } 212 213 switch (unexpandedPaths.size()) { 214 case 1: 215 final String mimeType = MenuExecutor.getMimeType(type); 216 if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) { 217 operation &= ~MediaObject.SUPPORT_EDIT; 218 } 219 break; 220 default: 221 operation &= SUPPORT_MULTIPLE_MASK; 222 } 223 224 return operation; 225 } 226 227 // Share intent needs to expand the selection set so we can get URI of 228 // each media item computeSharingIntent(JobContext jc)229 private Intent computeSharingIntent(JobContext jc) { 230 ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true); 231 if (expandedPaths.size() == 0) { 232 if (mNfcAdapter != null) { 233 mNfcAdapter.setBeamPushUris(null, (Activity)mActivity); 234 } 235 return null; 236 } 237 final ArrayList<Uri> uris = new ArrayList<Uri>(); 238 DataManager manager = mActivity.getDataManager(); 239 int type = 0; 240 final Intent intent = new Intent(); 241 for (Path path : expandedPaths) { 242 if (jc.isCancelled()) return null; 243 int support = manager.getSupportedOperations(path); 244 type |= manager.getMediaType(path); 245 246 if ((support & MediaObject.SUPPORT_SHARE) != 0) { 247 uris.add(manager.getContentUri(path)); 248 } 249 } 250 251 final int size = uris.size(); 252 if (size > 0) { 253 final String mimeType = MenuExecutor.getMimeType(type); 254 if (size > 1) { 255 intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType); 256 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); 257 } else { 258 intent.setAction(Intent.ACTION_SEND).setType(mimeType); 259 intent.putExtra(Intent.EXTRA_STREAM, uris.get(0)); 260 } 261 intent.setType(mimeType); 262 if (mNfcAdapter != null) { 263 mNfcAdapter.setBeamPushUris(uris.toArray(new Uri[uris.size()]), 264 (Activity)mActivity); 265 } 266 } else { 267 if (mNfcAdapter != null) { 268 mNfcAdapter.setBeamPushUris(null, (Activity)mActivity); 269 } 270 } 271 272 return intent; 273 } 274 updateSupportedOperation(Path path, boolean selected)275 public void updateSupportedOperation(Path path, boolean selected) { 276 // TODO: We need to improve the performance 277 updateSupportedOperation(); 278 } 279 updateSupportedOperation()280 public void updateSupportedOperation() { 281 // Interrupt previous unfinished task, mMenuTask is only accessed in main thread 282 if (mMenuTask != null) { 283 mMenuTask.cancel(); 284 } 285 286 updateSelectionMenu(); 287 288 // Disable share action until share intent is in good shape 289 final MenuItem item = mShareActionProvider != null ? 290 mMenu.findItem(R.id.action_share) : null; 291 final boolean supportShare = item != null; 292 if (supportShare) item.setEnabled(false); 293 294 // Generate sharing intent and update supported operations in the background 295 // The task can take a long time and be canceled in the mean time. 296 mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() { 297 public Void run(final JobContext jc) { 298 // Pass1: Deal with unexpanded media object list for menu operation. 299 final int operation = computeMenuOptions(jc); 300 301 // Pass2: Deal with expanded media object list for sharing operation. 302 final Intent intent = supportShare ? computeSharingIntent(jc) : null; 303 mMainHandler.post(new Runnable() { 304 public void run() { 305 mMenuTask = null; 306 if (!jc.isCancelled()) { 307 MenuExecutor.updateMenuOperation(mMenu, operation); 308 if (supportShare) { 309 item.setEnabled(true); 310 mShareActionProvider.setShareIntent(intent); 311 } 312 } 313 } 314 }); 315 return null; 316 } 317 }); 318 } 319 pause()320 public void pause() { 321 if (mMenuTask != null) { 322 mMenuTask.cancel(); 323 mMenuTask = null; 324 } 325 mMenuExecutor.pause(); 326 } 327 resume()328 public void resume() { 329 if (mSelectionManager.inSelectionMode()) updateSupportedOperation(); 330 } 331 } 332