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.app.AlertDialog; 21 import android.app.ProgressDialog; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.DialogInterface.OnCancelListener; 25 import android.content.DialogInterface.OnClickListener; 26 import android.content.Intent; 27 import android.os.Handler; 28 import android.os.Message; 29 import android.view.Menu; 30 import android.view.MenuItem; 31 32 import com.android.gallery3d.R; 33 import com.android.gallery3d.app.CropImage; 34 import com.android.gallery3d.app.GalleryActivity; 35 import com.android.gallery3d.common.Utils; 36 import com.android.gallery3d.data.DataManager; 37 import com.android.gallery3d.data.MediaItem; 38 import com.android.gallery3d.data.MediaObject; 39 import com.android.gallery3d.data.Path; 40 import com.android.gallery3d.util.Future; 41 import com.android.gallery3d.util.GalleryUtils; 42 import com.android.gallery3d.util.ThreadPool.Job; 43 import com.android.gallery3d.util.ThreadPool.JobContext; 44 45 import java.util.ArrayList; 46 47 public class MenuExecutor { 48 @SuppressWarnings("unused") 49 private static final String TAG = "MenuExecutor"; 50 51 private static final int MSG_TASK_COMPLETE = 1; 52 private static final int MSG_TASK_UPDATE = 2; 53 private static final int MSG_DO_SHARE = 3; 54 55 public static final int EXECUTION_RESULT_SUCCESS = 1; 56 public static final int EXECUTION_RESULT_FAIL = 2; 57 public static final int EXECUTION_RESULT_CANCEL = 3; 58 59 private ProgressDialog mDialog; 60 private Future<?> mTask; 61 // wait the operation to finish when we want to stop it. 62 private boolean mWaitOnStop; 63 64 private final GalleryActivity mActivity; 65 private final SelectionManager mSelectionManager; 66 private final Handler mHandler; 67 68 private static ProgressDialog createProgressDialog( 69 Context context, int titleId, int progressMax) { 70 ProgressDialog dialog = new ProgressDialog(context); 71 dialog.setTitle(titleId); 72 dialog.setMax(progressMax); 73 dialog.setCancelable(false); 74 dialog.setIndeterminate(false); 75 if (progressMax > 1) { 76 dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 77 } 78 return dialog; 79 } 80 81 public interface ProgressListener { 82 public void onConfirmDialogShown(); 83 public void onConfirmDialogDismissed(boolean confirmed); 84 public void onProgressUpdate(int index); 85 public void onProgressComplete(int result); 86 } 87 88 public MenuExecutor( 89 GalleryActivity activity, SelectionManager selectionManager) { 90 mActivity = Utils.checkNotNull(activity); 91 mSelectionManager = Utils.checkNotNull(selectionManager); 92 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 93 @Override 94 public void handleMessage(Message message) { 95 switch (message.what) { 96 case MSG_TASK_COMPLETE: { 97 stopTaskAndDismissDialog(); 98 if (message.obj != null) { 99 ProgressListener listener = (ProgressListener) message.obj; 100 listener.onProgressComplete(message.arg1); 101 } 102 mSelectionManager.leaveSelectionMode(); 103 break; 104 } 105 case MSG_TASK_UPDATE: { 106 if (mDialog != null) mDialog.setProgress(message.arg1); 107 if (message.obj != null) { 108 ProgressListener listener = (ProgressListener) message.obj; 109 listener.onProgressUpdate(message.arg1); 110 } 111 break; 112 } 113 case MSG_DO_SHARE: { 114 ((Activity) mActivity).startActivity((Intent) message.obj); 115 break; 116 } 117 } 118 } 119 }; 120 } 121 122 private void stopTaskAndDismissDialog() { 123 if (mTask != null) { 124 if (!mWaitOnStop) mTask.cancel(); 125 mTask.waitDone(); 126 mDialog.dismiss(); 127 mDialog = null; 128 mTask = null; 129 } 130 } 131 132 public void pause() { 133 stopTaskAndDismissDialog(); 134 } 135 136 private void onProgressUpdate(int index, ProgressListener listener) { 137 mHandler.sendMessage( 138 mHandler.obtainMessage(MSG_TASK_UPDATE, index, 0, listener)); 139 } 140 141 private void onProgressComplete(int result, ProgressListener listener) { 142 mHandler.sendMessage(mHandler.obtainMessage(MSG_TASK_COMPLETE, result, 0, listener)); 143 } 144 145 private static void setMenuItemVisibility( 146 Menu menu, int id, boolean visibility) { 147 MenuItem item = menu.findItem(id); 148 if (item != null) item.setVisible(visibility); 149 } 150 151 public static void updateMenuOperation(Menu menu, int supported) { 152 boolean supportDelete = (supported & MediaObject.SUPPORT_DELETE) != 0; 153 boolean supportRotate = (supported & MediaObject.SUPPORT_ROTATE) != 0; 154 boolean supportCrop = (supported & MediaObject.SUPPORT_CROP) != 0; 155 boolean supportShare = (supported & MediaObject.SUPPORT_SHARE) != 0; 156 boolean supportSetAs = (supported & MediaObject.SUPPORT_SETAS) != 0; 157 boolean supportShowOnMap = (supported & MediaObject.SUPPORT_SHOW_ON_MAP) != 0; 158 boolean supportCache = (supported & MediaObject.SUPPORT_CACHE) != 0; 159 boolean supportEdit = (supported & MediaObject.SUPPORT_EDIT) != 0; 160 boolean supportInfo = (supported & MediaObject.SUPPORT_INFO) != 0; 161 boolean supportImport = (supported & MediaObject.SUPPORT_IMPORT) != 0; 162 163 setMenuItemVisibility(menu, R.id.action_delete, supportDelete); 164 setMenuItemVisibility(menu, R.id.action_rotate_ccw, supportRotate); 165 setMenuItemVisibility(menu, R.id.action_rotate_cw, supportRotate); 166 setMenuItemVisibility(menu, R.id.action_crop, supportCrop); 167 setMenuItemVisibility(menu, R.id.action_share, supportShare); 168 setMenuItemVisibility(menu, R.id.action_setas, supportSetAs); 169 setMenuItemVisibility(menu, R.id.action_show_on_map, supportShowOnMap); 170 setMenuItemVisibility(menu, R.id.action_edit, supportEdit); 171 setMenuItemVisibility(menu, R.id.action_details, supportInfo); 172 setMenuItemVisibility(menu, R.id.action_import, supportImport); 173 } 174 175 private Path getSingleSelectedPath() { 176 ArrayList<Path> ids = mSelectionManager.getSelected(true); 177 Utils.assertTrue(ids.size() == 1); 178 return ids.get(0); 179 } 180 181 private Intent getIntentBySingleSelectedPath(String action) { 182 DataManager manager = mActivity.getDataManager(); 183 Path path = getSingleSelectedPath(); 184 String mimeType = getMimeType(manager.getMediaType(path)); 185 return new Intent(action).setDataAndType(manager.getContentUri(path), mimeType); 186 } 187 188 private void onMenuClicked(int action, ProgressListener listener) { 189 onMenuClicked(action, listener, false, true); 190 } 191 192 public void onMenuClicked(int action, ProgressListener listener, 193 boolean waitOnStop, boolean showDialog) { 194 int title; 195 switch (action) { 196 case R.id.action_select_all: 197 if (mSelectionManager.inSelectAllMode()) { 198 mSelectionManager.deSelectAll(); 199 } else { 200 mSelectionManager.selectAll(); 201 } 202 return; 203 case R.id.action_crop: { 204 Intent intent = getIntentBySingleSelectedPath(CropImage.ACTION_CROP); 205 ((Activity) mActivity).startActivity(intent); 206 return; 207 } 208 case R.id.action_edit: { 209 Intent intent = getIntentBySingleSelectedPath(Intent.ACTION_EDIT) 210 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 211 ((Activity) mActivity).startActivity(Intent.createChooser(intent, null)); 212 return; 213 } 214 case R.id.action_setas: { 215 Intent intent = getIntentBySingleSelectedPath(Intent.ACTION_ATTACH_DATA) 216 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 217 intent.putExtra("mimeType", intent.getType()); 218 Activity activity = (Activity) mActivity; 219 activity.startActivity(Intent.createChooser( 220 intent, activity.getString(R.string.set_as))); 221 return; 222 } 223 case R.id.action_delete: 224 title = R.string.delete; 225 break; 226 case R.id.action_rotate_cw: 227 title = R.string.rotate_right; 228 break; 229 case R.id.action_rotate_ccw: 230 title = R.string.rotate_left; 231 break; 232 case R.id.action_show_on_map: 233 title = R.string.show_on_map; 234 break; 235 case R.id.action_import: 236 title = R.string.Import; 237 break; 238 default: 239 return; 240 } 241 startAction(action, title, listener, waitOnStop, showDialog); 242 } 243 244 private class ConfirmDialogListener implements OnClickListener, OnCancelListener { 245 private final int mActionId; 246 private final ProgressListener mListener; 247 248 public ConfirmDialogListener(int actionId, ProgressListener listener) { 249 mActionId = actionId; 250 mListener = listener; 251 } 252 253 @Override 254 public void onClick(DialogInterface dialog, int which) { 255 if (which == DialogInterface.BUTTON_POSITIVE) { 256 if (mListener != null) { 257 mListener.onConfirmDialogDismissed(true); 258 } 259 onMenuClicked(mActionId, mListener); 260 } else { 261 if (mListener != null) { 262 mListener.onConfirmDialogDismissed(false); 263 } 264 } 265 } 266 267 @Override 268 public void onCancel(DialogInterface dialog) { 269 if (mListener != null) { 270 mListener.onConfirmDialogDismissed(false); 271 } 272 } 273 } 274 275 public void onMenuClicked(MenuItem menuItem, String confirmMsg, 276 final ProgressListener listener) { 277 final int action = menuItem.getItemId(); 278 279 if (confirmMsg != null) { 280 if (listener != null) listener.onConfirmDialogShown(); 281 ConfirmDialogListener cdl = new ConfirmDialogListener(action, listener); 282 new AlertDialog.Builder(mActivity.getAndroidContext()) 283 .setMessage(confirmMsg) 284 .setOnCancelListener(cdl) 285 .setPositiveButton(R.string.ok, cdl) 286 .setNegativeButton(R.string.cancel, cdl) 287 .create().show(); 288 } else { 289 onMenuClicked(action, listener); 290 } 291 } 292 293 public void startAction(int action, int title, ProgressListener listener) { 294 startAction(action, title, listener, false, true); 295 } 296 297 public void startAction(int action, int title, ProgressListener listener, 298 boolean waitOnStop, boolean showDialog) { 299 ArrayList<Path> ids = mSelectionManager.getSelected(false); 300 stopTaskAndDismissDialog(); 301 302 Activity activity = (Activity) mActivity; 303 mDialog = createProgressDialog(activity, title, ids.size()); 304 if (showDialog) { 305 mDialog.show(); 306 } 307 MediaOperation operation = new MediaOperation(action, ids, listener); 308 mTask = mActivity.getThreadPool().submit(operation, null); 309 mWaitOnStop = waitOnStop; 310 } 311 312 public static String getMimeType(int type) { 313 switch (type) { 314 case MediaObject.MEDIA_TYPE_IMAGE : 315 return "image/*"; 316 case MediaObject.MEDIA_TYPE_VIDEO : 317 return "video/*"; 318 default: return "*/*"; 319 } 320 } 321 322 private boolean execute( 323 DataManager manager, JobContext jc, int cmd, Path path) { 324 boolean result = true; 325 Log.v(TAG, "Execute cmd: " + cmd + " for " + path); 326 long startTime = System.currentTimeMillis(); 327 328 switch (cmd) { 329 case R.id.action_delete: 330 manager.delete(path); 331 break; 332 case R.id.action_rotate_cw: 333 manager.rotate(path, 90); 334 break; 335 case R.id.action_rotate_ccw: 336 manager.rotate(path, -90); 337 break; 338 case R.id.action_toggle_full_caching: { 339 MediaObject obj = manager.getMediaObject(path); 340 int cacheFlag = obj.getCacheFlag(); 341 if (cacheFlag == MediaObject.CACHE_FLAG_FULL) { 342 cacheFlag = MediaObject.CACHE_FLAG_SCREENNAIL; 343 } else { 344 cacheFlag = MediaObject.CACHE_FLAG_FULL; 345 } 346 obj.cache(cacheFlag); 347 break; 348 } 349 case R.id.action_show_on_map: { 350 MediaItem item = (MediaItem) manager.getMediaObject(path); 351 double latlng[] = new double[2]; 352 item.getLatLong(latlng); 353 if (GalleryUtils.isValidLocation(latlng[0], latlng[1])) { 354 GalleryUtils.showOnMap((Context) mActivity, latlng[0], latlng[1]); 355 } 356 break; 357 } 358 case R.id.action_import: { 359 MediaObject obj = manager.getMediaObject(path); 360 result = obj.Import(); 361 break; 362 } 363 default: 364 throw new AssertionError(); 365 } 366 Log.v(TAG, "It takes " + (System.currentTimeMillis() - startTime) + 367 " ms to execute cmd for " + path); 368 return result; 369 } 370 371 private class MediaOperation implements Job<Void> { 372 private final ArrayList<Path> mItems; 373 private final int mOperation; 374 private final ProgressListener mListener; 375 376 public MediaOperation(int operation, ArrayList<Path> items, 377 ProgressListener listener) { 378 mOperation = operation; 379 mItems = items; 380 mListener = listener; 381 } 382 383 public Void run(JobContext jc) { 384 int index = 0; 385 DataManager manager = mActivity.getDataManager(); 386 int result = EXECUTION_RESULT_SUCCESS; 387 try { 388 for (Path id : mItems) { 389 if (jc.isCancelled()) { 390 result = EXECUTION_RESULT_CANCEL; 391 break; 392 } 393 if (!execute(manager, jc, mOperation, id)) { 394 result = EXECUTION_RESULT_FAIL; 395 } 396 onProgressUpdate(index++, mListener); 397 } 398 } catch (Throwable th) { 399 Log.e(TAG, "failed to execute operation " + mOperation 400 + " : " + th); 401 } finally { 402 onProgressComplete(result, mListener); 403 } 404 return null; 405 } 406 } 407 } 408