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.support.v4.print.PrintHelper;
30import android.view.Menu;
31import android.view.MenuItem;
32
33import com.android.gallery3d.R;
34import com.android.gallery3d.app.AbstractGalleryActivity;
35import com.android.gallery3d.common.Utils;
36import com.android.gallery3d.data.DataManager;
37import com.android.gallery3d.data.MediaItem;
38import com.android.gallery3d.data.MediaObject;
39import com.android.gallery3d.data.Path;
40import com.android.gallery3d.filtershow.crop.CropActivity;
41import com.android.gallery3d.util.Future;
42import com.android.gallery3d.util.GalleryUtils;
43import com.android.gallery3d.util.ThreadPool.Job;
44import com.android.gallery3d.util.ThreadPool.JobContext;
45
46import java.util.ArrayList;
47
48public 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
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 {
84        public void onConfirmDialogShown();
85        public void onConfirmDialogDismissed(boolean confirmed);
86        public void onProgressStart();
87        public void onProgressUpdate(int index);
88        public void onProgressComplete(int result);
89    }
90
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
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
141    public void resume() {
142        mPaused = false;
143        if (mDialog != null) mDialog.show();
144    }
145
146    public void pause() {
147        mPaused = true;
148        if (mDialog != null && mDialog.isShowing()) mDialog.hide();
149    }
150
151    public void destroy() {
152        stopTaskAndDismissDialog();
153    }
154
155    private void onProgressUpdate(int index, ProgressListener listener) {
156        mHandler.sendMessage(
157                mHandler.obtainMessage(MSG_TASK_UPDATE, index, 0, listener));
158    }
159
160    private void onProgressStart(ProgressListener listener) {
161        mHandler.sendMessage(mHandler.obtainMessage(MSG_TASK_START, listener));
162    }
163
164    private void onProgressComplete(int result, ProgressListener listener) {
165        mHandler.sendMessage(mHandler.obtainMessage(MSG_TASK_COMPLETE, result, 0, listener));
166    }
167
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
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
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
214    private Path getSingleSelectedPath() {
215        ArrayList<Path> ids = mSelectionManager.getSelected(true);
216        Utils.assertTrue(ids.size() == 1);
217        return ids.get(0);
218    }
219
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
227    private void onMenuClicked(int action, ProgressListener listener) {
228        onMenuClicked(action, listener, false, true);
229    }
230
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
284        public ConfirmDialogListener(int actionId, ProgressListener listener) {
285            mActionId = actionId;
286            mListener = listener;
287        }
288
289        @Override
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
304        public void onCancel(DialogInterface dialog) {
305            if (mListener != null) {
306                mListener.onConfirmDialogDismissed(false);
307            }
308        }
309    }
310
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
329    public void startAction(int action, int title, ProgressListener listener) {
330        startAction(action, title, listener, false, true);
331    }
332
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
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
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
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
418        public MediaOperation(int operation, ArrayList<Path> items,
419                ProgressListener listener) {
420            mOperation = operation;
421            mItems = items;
422            mListener = listener;
423        }
424
425        @Override
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