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