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.ShareActionProvider;
32import android.widget.PopupMenu.OnMenuItemClickListener;
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            public boolean onMenuItemClick(MenuItem item) {
92                return onActionItemClicked(actionMode, item);
93            }
94        });
95        return actionMode;
96    }
97
98    public void setTitle(String title) {
99        mSelectionMenu.setTitle(title);
100    }
101
102    public void setActionModeListener(ActionModeListener listener) {
103        mListener = listener;
104    }
105
106    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
107        boolean result;
108        if (mListener != null) {
109            result = mListener.onActionItemClicked(item);
110            if (result) {
111                mSelectionManager.leaveSelectionMode();
112                return result;
113            }
114        }
115        ProgressListener listener = null;
116        if (item.getItemId() == R.id.action_import) {
117            listener = new ImportCompleteListener(mActivity);
118        }
119        result = mMenuExecutor.onMenuClicked(item, listener);
120        if (item.getItemId() == R.id.action_select_all) {
121            updateSupportedOperation();
122            updateSelectionMenu();
123        }
124        return result;
125    }
126
127    private void updateSelectionMenu() {
128        // update title
129        int count = mSelectionManager.getSelectedCount();
130        String format = mActivity.getResources().getQuantityString(
131                R.plurals.number_of_items_selected, count);
132        setTitle(String.format(format, count));
133        // For clients who call SelectionManager.selectAll() directly, we need to ensure the
134        // menu status is consistent with selection manager.
135        MenuItem item = mSelectionMenu.findItem(R.id.action_select_all);
136        if (item != null) {
137            if (mSelectionManager.inSelectAllMode()) {
138                item.setChecked(true);
139                item.setTitle(R.string.deselect_all);
140            } else {
141                item.setChecked(false);
142                item.setTitle(R.string.select_all);
143            }
144        }
145    }
146
147    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
148        MenuInflater inflater = mode.getMenuInflater();
149        inflater.inflate(R.menu.operation, menu);
150
151        mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
152        OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() {
153            public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
154                mSelectionManager.leaveSelectionMode();
155                return false;
156            }
157        };
158
159        mShareActionProvider.setOnShareTargetSelectedListener(listener);
160        mMenu = menu;
161        return true;
162    }
163
164    public void onDestroyActionMode(ActionMode mode) {
165        mSelectionManager.leaveSelectionMode();
166    }
167
168    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
169        return true;
170    }
171
172    // Menu options are determined by selection set itself.
173    // We cannot expand it because MenuExecuter executes it based on
174    // the selection set instead of the expanded result.
175    // e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't.
176    private void updateMenuOptions(JobContext jc) {
177        ArrayList<Path> paths = mSelectionManager.getSelected(false);
178
179        int operation = MediaObject.SUPPORT_ALL;
180        DataManager manager = mActivity.getDataManager();
181        int type = 0;
182        for (Path path : paths) {
183            if (jc.isCancelled()) return;
184            int support = manager.getSupportedOperations(path);
185            type |= manager.getMediaType(path);
186            operation &= support;
187        }
188
189        final String mimeType = MenuExecutor.getMimeType(type);
190        if (paths.size() == 0) {
191            operation = 0;
192        } else if (paths.size() == 1) {
193            if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) {
194                operation &= ~MediaObject.SUPPORT_EDIT;
195            }
196        } else {
197            operation &= SUPPORT_MULTIPLE_MASK;
198        }
199
200        final int supportedOperation = operation;
201
202        mMainHandler.post(new Runnable() {
203            @Override
204            public void run() {
205                mMenuTask = null;
206                MenuExecutor.updateMenuOperation(mMenu, supportedOperation);
207            }
208        });
209    }
210
211    // Share intent needs to expand the selection set so we can get URI of
212    // each media item
213    private void updateSharingIntent(JobContext jc) {
214        if (mShareActionProvider == null) return;
215        ArrayList<Path> paths = mSelectionManager.getSelected(true);
216        if (paths.size() == 0) return;
217
218        final ArrayList<Uri> uris = new ArrayList<Uri>();
219
220        DataManager manager = mActivity.getDataManager();
221        int type = 0;
222
223        final Intent intent = new Intent();
224        for (Path path : paths) {
225            int support = manager.getSupportedOperations(path);
226            type |= manager.getMediaType(path);
227
228            if ((support & MediaObject.SUPPORT_SHARE) != 0) {
229                uris.add(manager.getContentUri(path));
230            }
231        }
232
233        final int size = uris.size();
234        if (size > 0) {
235            final String mimeType = MenuExecutor.getMimeType(type);
236            if (size > 1) {
237                intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType);
238                intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
239            } else {
240                intent.setAction(Intent.ACTION_SEND).setType(mimeType);
241                intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
242            }
243            intent.setType(mimeType);
244
245            mMainHandler.post(new Runnable() {
246                @Override
247                public void run() {
248                    Log.v(TAG, "Sharing intent is ready: action = " + intent.getAction());
249                    mShareActionProvider.setShareIntent(intent);
250                }
251            });
252        }
253    }
254
255    public void updateSupportedOperation(Path path, boolean selected) {
256        // TODO: We need to improve the performance
257        updateSupportedOperation();
258    }
259
260    public void updateSupportedOperation() {
261        if (mMenuTask != null) {
262            mMenuTask.cancel();
263        }
264
265        // Disable share action until share intent is in good shape
266        if (mShareActionProvider != null) {
267            Log.v(TAG, "Disable sharing until intent is ready");
268            mShareActionProvider.setShareIntent(null);
269        }
270
271        // Generate sharing intent and update supported operations in the background
272        mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() {
273            public Void run(JobContext jc) {
274                updateMenuOptions(jc);
275                updateSharingIntent(jc);
276                return null;
277            }
278        });
279    }
280
281    public void pause() {
282        if (mMenuTask != null) {
283            mMenuTask.cancel();
284            mMenuTask = null;
285        }
286        mMenuExecutor.pause();
287    }
288
289    public void resume() {
290        if (mSelectionManager.inSelectionMode()) updateSupportedOperation();
291    }
292}
293