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