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