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