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.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; 31 import android.widget.ShareActionProvider; 32 import android.widget.PopupMenu.OnMenuItemClickListener; 33 import android.widget.ShareActionProvider.OnShareTargetSelectedListener; 34 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 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; 68 private final Handler mMainHandler; 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); 89 updateSelectionMenu(); 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(); 122 updateSelectionMenu(); 123 } 124 return result; 125 } 126 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)); 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 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); 152 OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() { 153 public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) { 154 mSelectionManager.leaveSelectionMode(); 155 return false; 156 } 157 }; 158 159 mShareActionProvider.setOnShareTargetSelectedListener(listener); 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 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. 176 private void updateMenuOptions(JobContext jc) { 177 ArrayList<Path> paths = mSelectionManager.getSelected(false); 178 179 int operation = MediaObject.SUPPORT_ALL; 180 DataManager manager = mActivity.getDataManager(); 181 int type = 0; 182 for (Path path : paths) { 183 if (jc.isCancelled()) return; 184 int support = manager.getSupportedOperations(path); 185 type |= manager.getMediaType(path); 186 operation &= support; 187 } 188 189 final String mimeType = MenuExecutor.getMimeType(type); 190 if (paths.size() == 0) { 191 operation = 0; 192 } else if (paths.size() == 1) { 193 if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) { 194 operation &= ~MediaObject.SUPPORT_EDIT; 195 } 196 } else { 197 operation &= SUPPORT_MULTIPLE_MASK; 198 } 199 200 final int supportedOperation = operation; 201 202 mMainHandler.post(new Runnable() { 203 @Override 204 public void run() { 205 mMenuTask = null; 206 MenuExecutor.updateMenuOperation(mMenu, supportedOperation); 207 } 208 }); 209 } 210 211 // Share intent needs to expand the selection set so we can get URI of 212 // each media item 213 private void updateSharingIntent(JobContext jc) { 214 if (mShareActionProvider == null) return; 215 ArrayList<Path> paths = mSelectionManager.getSelected(true); 216 if (paths.size() == 0) return; 217 218 final ArrayList<Uri> uris = new ArrayList<Uri>(); 219 220 DataManager manager = mActivity.getDataManager(); 221 int type = 0; 222 223 final Intent intent = new Intent(); 224 for (Path path : paths) { 225 int support = manager.getSupportedOperations(path); 226 type |= manager.getMediaType(path); 227 228 if ((support & MediaObject.SUPPORT_SHARE) != 0) { 229 uris.add(manager.getContentUri(path)); 230 } 231 } 232 233 final int size = uris.size(); 234 if (size > 0) { 235 final String mimeType = MenuExecutor.getMimeType(type); 236 if (size > 1) { 237 intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType); 238 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); 239 } else { 240 intent.setAction(Intent.ACTION_SEND).setType(mimeType); 241 intent.putExtra(Intent.EXTRA_STREAM, uris.get(0)); 242 } 243 intent.setType(mimeType); 244 245 mMainHandler.post(new Runnable() { 246 @Override 247 public void run() { 248 Log.v(TAG, "Sharing intent is ready: action = " + intent.getAction()); 249 mShareActionProvider.setShareIntent(intent); 250 } 251 }); 252 } 253 } 254 255 public void updateSupportedOperation(Path path, boolean selected) { 256 // TODO: We need to improve the performance 257 updateSupportedOperation(); 258 } 259 260 public void updateSupportedOperation() { 261 if (mMenuTask != null) { 262 mMenuTask.cancel(); 263 } 264 265 // Disable share action until share intent is in good shape 266 if (mShareActionProvider != null) { 267 Log.v(TAG, "Disable sharing until intent is ready"); 268 mShareActionProvider.setShareIntent(null); 269 } 270 271 // Generate sharing intent and update supported operations in the background 272 mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() { 273 public Void run(JobContext jc) { 274 updateMenuOptions(jc); 275 updateSharingIntent(jc); 276 return null; 277 } 278 }); 279 } 280 281 public void pause() { 282 if (mMenuTask != null) { 283 mMenuTask.cancel(); 284 mMenuTask = null; 285 } 286 mMenuExecutor.pause(); 287 } 288 289 public void resume() { 290 if (mSelectionManager.inSelectionMode()) updateSupportedOperation(); 291 } 292 } 293