ActionModeHandler.java revision 67098d1a72fd04e2af06d3a5939cde28c65f70d9
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.content.Context;
21import android.content.Intent;
22import android.net.Uri;
23import android.os.Handler;
24import android.view.ActionMode;
25import android.view.LayoutInflater;
26import android.view.Menu;
27import android.view.MenuInflater;
28import android.view.MenuItem;
29import android.view.View;
30import android.widget.Button;
31import android.widget.PopupMenu.OnMenuItemClickListener;
32import android.widget.ShareActionProvider;
33import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
34
35import com.android.gallery3d.R;
36import com.android.gallery3d.app.GalleryActionBar;
37import com.android.gallery3d.app.GalleryActivity;
38import com.android.gallery3d.common.Utils;
39import com.android.gallery3d.data.DataManager;
40import com.android.gallery3d.data.MediaObject;
41import com.android.gallery3d.data.Path;
42import com.android.gallery3d.ui.CustomMenu.DropDownMenu;
43import com.android.gallery3d.ui.MenuExecutor.ProgressListener;
44import com.android.gallery3d.util.Future;
45import com.android.gallery3d.util.GalleryUtils;
46import com.android.gallery3d.util.ThreadPool.Job;
47import com.android.gallery3d.util.ThreadPool.JobContext;
48
49import java.util.ArrayList;
50
51public 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            @Override
92            public boolean onMenuItemClick(MenuItem item) {
93                return onActionItemClicked(actionMode, item);
94            }
95        });
96        return actionMode;
97    }
98
99    public void setTitle(String title) {
100        mSelectionMenu.setTitle(title);
101    }
102
103    public void setActionModeListener(ActionModeListener listener) {
104        mListener = listener;
105    }
106
107    @Override
108    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
109        GLRoot root = mActivity.getGLRoot();
110        root.lockRenderThread();
111        try {
112            boolean result;
113            // Give listener a chance to process this command before it's routed to
114            // ActionModeHandler, which handles command only based on the action id.
115            // Sometimes the listener may have more background information to handle
116            // an action command.
117            if (mListener != null) {
118                result = mListener.onActionItemClicked(item);
119                if (result) {
120                    mSelectionManager.leaveSelectionMode();
121                    return result;
122                }
123            }
124            ProgressListener listener = null;
125            boolean needsConfirm = false;
126            int action = item.getItemId();
127            if (action == R.id.action_import) {
128                listener = new ImportCompleteListener(mActivity);
129            } else if (item.getItemId() == R.id.action_delete) {
130                needsConfirm = true;
131            }
132            mMenuExecutor.onMenuClicked(item, needsConfirm, listener);
133            if (action == R.id.action_select_all) {
134                updateSupportedOperation();
135                updateSelectionMenu();
136            }
137        } finally {
138            root.unlockRenderThread();
139        }
140        return true;
141    }
142
143    private void updateSelectionMenu() {
144        // update title
145        int count = mSelectionManager.getSelectedCount();
146        String format = mActivity.getResources().getQuantityString(
147                R.plurals.number_of_items_selected, count);
148        setTitle(String.format(format, count));
149        // For clients who call SelectionManager.selectAll() directly, we need to ensure the
150        // menu status is consistent with selection manager.
151        MenuItem item = mSelectionMenu.findItem(R.id.action_select_all);
152        if (item != null) {
153            if (mSelectionManager.inSelectAllMode()) {
154                item.setChecked(true);
155                item.setTitle(R.string.deselect_all);
156            } else {
157                item.setChecked(false);
158                item.setTitle(R.string.select_all);
159            }
160        }
161    }
162
163    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
164        MenuInflater inflater = mode.getMenuInflater();
165        inflater.inflate(R.menu.operation, menu);
166
167        mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
168        OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() {
169            public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
170                mSelectionManager.leaveSelectionMode();
171                return false;
172            }
173        };
174
175        mShareActionProvider.setOnShareTargetSelectedListener(listener);
176        mMenu = menu;
177        return true;
178    }
179
180    public void onDestroyActionMode(ActionMode mode) {
181        mSelectionManager.leaveSelectionMode();
182    }
183
184    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
185        return true;
186    }
187
188    // Menu options are determined by selection set itself.
189    // We cannot expand it because MenuExecuter executes it based on
190    // the selection set instead of the expanded result.
191    // e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't.
192    private int computeMenuOptions(JobContext jc) {
193        ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false);
194        if (unexpandedPaths.isEmpty()) {
195            // This happens when starting selection mode from overflow menu
196            // (instead of long press a media object)
197            return 0;
198        }
199        int operation = MediaObject.SUPPORT_ALL;
200        DataManager manager = mActivity.getDataManager();
201        int type = 0;
202        for (Path path : unexpandedPaths) {
203            if (jc.isCancelled()) return 0;
204            int support = manager.getSupportedOperations(path);
205            type |= manager.getMediaType(path);
206            operation &= support;
207        }
208
209        switch (unexpandedPaths.size()) {
210            case 1:
211                final String mimeType = MenuExecutor.getMimeType(type);
212                if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) {
213                    operation &= ~MediaObject.SUPPORT_EDIT;
214                }
215                break;
216            default:
217                operation &= SUPPORT_MULTIPLE_MASK;
218        }
219
220        return operation;
221    }
222
223    // Share intent needs to expand the selection set so we can get URI of
224    // each media item
225    private Intent computeSharingIntent(JobContext jc) {
226        ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true);
227        if (expandedPaths.size() == 0) return null;
228        final ArrayList<Uri> uris = new ArrayList<Uri>();
229        DataManager manager = mActivity.getDataManager();
230        int type = 0;
231        final Intent intent = new Intent();
232        for (Path path : expandedPaths) {
233            if (jc.isCancelled()) return null;
234            int support = manager.getSupportedOperations(path);
235            type |= manager.getMediaType(path);
236
237            if ((support & MediaObject.SUPPORT_SHARE) != 0) {
238                uris.add(manager.getContentUri(path));
239            }
240        }
241
242        final int size = uris.size();
243        if (size > 0) {
244            final String mimeType = MenuExecutor.getMimeType(type);
245            if (size > 1) {
246                intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType);
247                intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
248            } else {
249                intent.setAction(Intent.ACTION_SEND).setType(mimeType);
250                intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
251            }
252            intent.setType(mimeType);
253        }
254
255        return intent;
256    }
257
258    public void updateSupportedOperation(Path path, boolean selected) {
259        // TODO: We need to improve the performance
260        updateSupportedOperation();
261    }
262
263    public void updateSupportedOperation() {
264        // Interrupt previous unfinished task, mMenuTask is only accessed in main thread
265        if (mMenuTask != null) {
266            mMenuTask.cancel();
267        }
268
269        // Disable share action until share intent is in good shape
270        final MenuItem item = mShareActionProvider != null ?
271                mMenu.findItem(R.id.action_share) : null;
272        final boolean supportShare = item != null;
273        if (supportShare) item.setEnabled(false);
274
275        // Generate sharing intent and update supported operations in the background
276        // The task can take a long time and be canceled in the mean time.
277        mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() {
278            public Void run(final JobContext jc) {
279                // Pass1: Deal with unexpanded media object list for menu operation.
280                final int operation = computeMenuOptions(jc);
281
282                // Pass2: Deal with expanded media object list for sharing operation.
283                final Intent intent = supportShare ? computeSharingIntent(jc) : null;
284                mMainHandler.post(new Runnable() {
285                    public void run() {
286                        mMenuTask = null;
287                        if (!jc.isCancelled()) {
288                            MenuExecutor.updateMenuOperation(mMenu, operation);
289                            if (supportShare) {
290                                item.setEnabled(true);
291                                mShareActionProvider.setShareIntent(intent);
292                            }
293                        }
294                    }
295                });
296                return null;
297            }
298        });
299    }
300
301    public void pause() {
302        if (mMenuTask != null) {
303            mMenuTask.cancel();
304            mMenuTask = null;
305        }
306        mMenuExecutor.pause();
307    }
308
309    public void resume() {
310        if (mSelectionManager.inSelectionMode()) updateSupportedOperation();
311    }
312}
313