• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.gallery3d.ui;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.Intent;
22 import android.net.Uri;
23 import android.nfc.NfcAdapter;
24 import android.os.Handler;
25 import android.view.ActionMode;
26 import android.view.ActionMode.Callback;
27 import android.view.LayoutInflater;
28 import android.view.Menu;
29 import android.view.MenuItem;
30 import android.view.View;
31 import android.widget.Button;
32 import android.widget.ShareActionProvider;
33 import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
34 
35 import com.android.gallery3d.R;
36 import com.android.gallery3d.app.AbstractGalleryActivity;
37 import com.android.gallery3d.common.ApiHelper;
38 import com.android.gallery3d.common.Utils;
39 import com.android.gallery3d.data.DataManager;
40 import com.android.gallery3d.data.MediaObject;
41 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
42 import com.android.gallery3d.data.Path;
43 import com.android.gallery3d.ui.MenuExecutor.ProgressListener;
44 import com.android.gallery3d.util.Future;
45 import com.android.gallery3d.util.GalleryUtils;
46 import com.android.gallery3d.util.ThreadPool.Job;
47 import com.android.gallery3d.util.ThreadPool.JobContext;
48 
49 import java.util.ArrayList;
50 
51 public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickListener {
52 
53     @SuppressWarnings("unused")
54     private static final String TAG = "ActionModeHandler";
55 
56     private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE
57             | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE
58             | MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT;
59 
60     public interface ActionModeListener {
onActionItemClicked(MenuItem item)61         public boolean onActionItemClicked(MenuItem item);
62     }
63 
64     private final AbstractGalleryActivity mActivity;
65     private final MenuExecutor mMenuExecutor;
66     private final SelectionManager mSelectionManager;
67     private final NfcAdapter mNfcAdapter;
68     private Menu mMenu;
69     private MenuItem mSharePanoramaMenuItem;
70     private MenuItem mShareMenuItem;
71     private ShareActionProvider mSharePanoramaActionProvider;
72     private ShareActionProvider mShareActionProvider;
73     private SelectionMenu mSelectionMenu;
74     private ActionModeListener mListener;
75     private Future<?> mMenuTask;
76     private final Handler mMainHandler;
77     private ActionMode mActionMode;
78 
79     private static class GetAllPanoramaSupports implements PanoramaSupportCallback {
80         private int mNumInfoRequired;
81         private JobContext mJobContext;
82         public boolean mAllPanoramas = true;
83         public boolean mAllPanorama360 = true;
84         public boolean mHasPanorama360 = false;
85         private Object mLock = new Object();
86 
GetAllPanoramaSupports(ArrayList<MediaObject> mediaObjects, JobContext jc)87         public GetAllPanoramaSupports(ArrayList<MediaObject> mediaObjects, JobContext jc) {
88             mJobContext = jc;
89             mNumInfoRequired = mediaObjects.size();
90             for (MediaObject mediaObject : mediaObjects) {
91                 mediaObject.getPanoramaSupport(this);
92             }
93         }
94 
95         @Override
panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, boolean isPanorama360)96         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
97                 boolean isPanorama360) {
98             synchronized (mLock) {
99                 mNumInfoRequired--;
100                 mAllPanoramas = isPanorama && mAllPanoramas;
101                 mAllPanorama360 = isPanorama360 && mAllPanorama360;
102                 mHasPanorama360 = mHasPanorama360 || isPanorama360;
103                 if (mNumInfoRequired == 0 || mJobContext.isCancelled()) {
104                     mLock.notifyAll();
105                 }
106             }
107         }
108 
waitForPanoramaSupport()109         public void waitForPanoramaSupport() {
110             synchronized (mLock) {
111                 while (mNumInfoRequired != 0 && !mJobContext.isCancelled()) {
112                     try {
113                         mLock.wait();
114                     } catch (InterruptedException e) {
115                         // May be a cancelled job context
116                     }
117                 }
118             }
119         }
120     }
121 
ActionModeHandler( AbstractGalleryActivity activity, SelectionManager selectionManager)122     public ActionModeHandler(
123             AbstractGalleryActivity activity, SelectionManager selectionManager) {
124         mActivity = Utils.checkNotNull(activity);
125         mSelectionManager = Utils.checkNotNull(selectionManager);
126         mMenuExecutor = new MenuExecutor(activity, selectionManager);
127         mMainHandler = new Handler(activity.getMainLooper());
128         mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
129     }
130 
startActionMode()131     public void startActionMode() {
132         Activity a = mActivity;
133         mActionMode = a.startActionMode(this);
134         View customView = LayoutInflater.from(a).inflate(
135                 R.layout.action_mode, null);
136         mActionMode.setCustomView(customView);
137         mSelectionMenu = new SelectionMenu(a,
138                 (Button) customView.findViewById(R.id.selection_menu), this);
139         updateSelectionMenu();
140     }
141 
finishActionMode()142     public void finishActionMode() {
143         mActionMode.finish();
144     }
145 
setTitle(String title)146     public void setTitle(String title) {
147         mSelectionMenu.setTitle(title);
148     }
149 
setActionModeListener(ActionModeListener listener)150     public void setActionModeListener(ActionModeListener listener) {
151         mListener = listener;
152     }
153 
154     private WakeLockHoldingProgressListener mDeleteProgressListener;
155 
156     @Override
onActionItemClicked(ActionMode mode, MenuItem item)157     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
158         GLRoot root = mActivity.getGLRoot();
159         root.lockRenderThread();
160         try {
161             boolean result;
162             // Give listener a chance to process this command before it's routed to
163             // ActionModeHandler, which handles command only based on the action id.
164             // Sometimes the listener may have more background information to handle
165             // an action command.
166             if (mListener != null) {
167                 result = mListener.onActionItemClicked(item);
168                 if (result) {
169                     mSelectionManager.leaveSelectionMode();
170                     return result;
171                 }
172             }
173             ProgressListener listener = null;
174             String confirmMsg = null;
175             int action = item.getItemId();
176             if (action == R.id.action_import) {
177                 listener = new ImportCompleteListener(mActivity);
178             } else if (action == R.id.action_delete) {
179                 confirmMsg = mActivity.getResources().getQuantityString(
180                         R.plurals.delete_selection, mSelectionManager.getSelectedCount());
181                 if (mDeleteProgressListener == null) {
182                     mDeleteProgressListener = new WakeLockHoldingProgressListener(mActivity,
183                             "Gallery Delete Progress Listener");
184                 }
185                 listener = mDeleteProgressListener;
186             }
187             mMenuExecutor.onMenuClicked(item, confirmMsg, listener);
188         } finally {
189             root.unlockRenderThread();
190         }
191         return true;
192     }
193 
194     @Override
onPopupItemClick(int itemId)195     public boolean onPopupItemClick(int itemId) {
196         GLRoot root = mActivity.getGLRoot();
197         root.lockRenderThread();
198         try {
199             if (itemId == R.id.action_select_all) {
200                 updateSupportedOperation();
201                 mMenuExecutor.onMenuClicked(itemId, null, false, true);
202             }
203             return true;
204         } finally {
205             root.unlockRenderThread();
206         }
207     }
208 
updateSelectionMenu()209     private void updateSelectionMenu() {
210         // update title
211         int count = mSelectionManager.getSelectedCount();
212         String format = mActivity.getResources().getQuantityString(
213                 R.plurals.number_of_items_selected, count);
214         setTitle(String.format(format, count));
215 
216         // For clients who call SelectionManager.selectAll() directly, we need to ensure the
217         // menu status is consistent with selection manager.
218         mSelectionMenu.updateSelectAllMode(mSelectionManager.inSelectAllMode());
219     }
220 
221     private final OnShareTargetSelectedListener mShareTargetSelectedListener =
222             new OnShareTargetSelectedListener() {
223         @Override
224         public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
225             mSelectionManager.leaveSelectionMode();
226             return false;
227         }
228     };
229 
230     @Override
onPrepareActionMode(ActionMode mode, Menu menu)231     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
232         return false;
233     }
234 
235     @Override
onCreateActionMode(ActionMode mode, Menu menu)236     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
237         mode.getMenuInflater().inflate(R.menu.operation, menu);
238 
239         mMenu = menu;
240         mSharePanoramaMenuItem = menu.findItem(R.id.action_share_panorama);
241         if (mSharePanoramaMenuItem != null) {
242             mSharePanoramaActionProvider = (ShareActionProvider) mSharePanoramaMenuItem
243                 .getActionProvider();
244             mSharePanoramaActionProvider.setOnShareTargetSelectedListener(
245                     mShareTargetSelectedListener);
246             mSharePanoramaActionProvider.setShareHistoryFileName("panorama_share_history.xml");
247         }
248         mShareMenuItem = menu.findItem(R.id.action_share);
249         if (mShareMenuItem != null) {
250             mShareActionProvider = (ShareActionProvider) mShareMenuItem
251                 .getActionProvider();
252             mShareActionProvider.setOnShareTargetSelectedListener(
253                     mShareTargetSelectedListener);
254             mShareActionProvider.setShareHistoryFileName("share_history.xml");
255         }
256         return true;
257     }
258 
259     @Override
onDestroyActionMode(ActionMode mode)260     public void onDestroyActionMode(ActionMode mode) {
261         mSelectionManager.leaveSelectionMode();
262     }
263 
getSelectedMediaObjects(JobContext jc)264     private ArrayList<MediaObject> getSelectedMediaObjects(JobContext jc) {
265         ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false);
266         if (unexpandedPaths.isEmpty()) {
267             // This happens when starting selection mode from overflow menu
268             // (instead of long press a media object)
269             return null;
270         }
271         ArrayList<MediaObject> selected = new ArrayList<MediaObject>();
272         DataManager manager = mActivity.getDataManager();
273         for (Path path : unexpandedPaths) {
274             if (jc.isCancelled()) {
275                 return null;
276             }
277             selected.add(manager.getMediaObject(path));
278         }
279 
280         return selected;
281     }
282     // Menu options are determined by selection set itself.
283     // We cannot expand it because MenuExecuter executes it based on
284     // the selection set instead of the expanded result.
285     // e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't.
computeMenuOptions(ArrayList<MediaObject> selected)286     private int computeMenuOptions(ArrayList<MediaObject> selected) {
287         int operation = MediaObject.SUPPORT_ALL;
288         int type = 0;
289         for (MediaObject mediaObject: selected) {
290             int support = mediaObject.getSupportedOperations();
291             type |= mediaObject.getMediaType();
292             operation &= support;
293         }
294 
295         switch (selected.size()) {
296             case 1:
297                 final String mimeType = MenuExecutor.getMimeType(type);
298                 if (!GalleryUtils.isEditorAvailable(mActivity, mimeType)) {
299                     operation &= ~MediaObject.SUPPORT_EDIT;
300                 }
301                 break;
302             default:
303                 operation &= SUPPORT_MULTIPLE_MASK;
304         }
305 
306         return operation;
307     }
308 
309     @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
setNfcBeamPushUris(Uri[] uris)310     private void setNfcBeamPushUris(Uri[] uris) {
311         if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
312             mNfcAdapter.setBeamPushUrisCallback(null, mActivity);
313             mNfcAdapter.setBeamPushUris(uris, mActivity);
314         }
315     }
316 
317     // Share intent needs to expand the selection set so we can get URI of
318     // each media item
computePanoramaSharingIntent(JobContext jc)319     private Intent computePanoramaSharingIntent(JobContext jc) {
320         ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true);
321         if (expandedPaths.size() == 0) {
322             return null;
323         }
324         final ArrayList<Uri> uris = new ArrayList<Uri>();
325         DataManager manager = mActivity.getDataManager();
326         final Intent intent = new Intent();
327         for (Path path : expandedPaths) {
328             if (jc.isCancelled()) return null;
329             uris.add(manager.getContentUri(path));
330         }
331 
332         final int size = uris.size();
333         if (size > 0) {
334             if (size > 1) {
335                 intent.setAction(Intent.ACTION_SEND_MULTIPLE);
336                 intent.setType(GalleryUtils.MIME_TYPE_PANORAMA360);
337                 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
338             } else {
339                 intent.setAction(Intent.ACTION_SEND);
340                 intent.setType(GalleryUtils.MIME_TYPE_PANORAMA360);
341                 intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
342             }
343             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
344         }
345 
346         return intent;
347     }
348 
computeSharingIntent(JobContext jc)349     private Intent computeSharingIntent(JobContext jc) {
350         ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true);
351         if (expandedPaths.size() == 0) {
352             setNfcBeamPushUris(null);
353             return null;
354         }
355         final ArrayList<Uri> uris = new ArrayList<Uri>();
356         DataManager manager = mActivity.getDataManager();
357         int type = 0;
358         final Intent intent = new Intent();
359         for (Path path : expandedPaths) {
360             if (jc.isCancelled()) return null;
361             int support = manager.getSupportedOperations(path);
362             type |= manager.getMediaType(path);
363 
364             if ((support & MediaObject.SUPPORT_SHARE) != 0) {
365                 uris.add(manager.getContentUri(path));
366             }
367         }
368 
369         final int size = uris.size();
370         if (size > 0) {
371             final String mimeType = MenuExecutor.getMimeType(type);
372             if (size > 1) {
373                 intent.setAction(Intent.ACTION_SEND_MULTIPLE).setType(mimeType);
374                 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
375             } else {
376                 intent.setAction(Intent.ACTION_SEND).setType(mimeType);
377                 intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
378             }
379             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
380             setNfcBeamPushUris(uris.toArray(new Uri[uris.size()]));
381         } else {
382             setNfcBeamPushUris(null);
383         }
384 
385         return intent;
386     }
387 
updateSupportedOperation(Path path, boolean selected)388     public void updateSupportedOperation(Path path, boolean selected) {
389         // TODO: We need to improve the performance
390         updateSupportedOperation();
391     }
392 
updateSupportedOperation()393     public void updateSupportedOperation() {
394         // Interrupt previous unfinished task, mMenuTask is only accessed in main thread
395         if (mMenuTask != null) mMenuTask.cancel();
396 
397         updateSelectionMenu();
398 
399         // Disable share actions until share intent is in good shape
400         if (mSharePanoramaMenuItem != null) mSharePanoramaMenuItem.setEnabled(false);
401         if (mShareMenuItem != null) mShareMenuItem.setEnabled(false);
402 
403         // Generate sharing intent and update supported operations in the background
404         // The task can take a long time and be canceled in the mean time.
405         mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() {
406             @Override
407             public Void run(final JobContext jc) {
408                 // Pass1: Deal with unexpanded media object list for menu operation.
409                 ArrayList<MediaObject> selected = getSelectedMediaObjects(jc);
410                 if (selected == null) {
411                     return null;
412                 }
413                 final int operation = computeMenuOptions(selected);
414                 if (jc.isCancelled()) {
415                     return null;
416                 }
417                 final GetAllPanoramaSupports supportCallback = new GetAllPanoramaSupports(selected,
418                         jc);
419 
420                 // Pass2: Deal with expanded media object list for sharing operation.
421                 final Intent share_panorama_intent = computePanoramaSharingIntent(jc);
422                 final Intent share_intent = computeSharingIntent(jc);
423 
424                 supportCallback.waitForPanoramaSupport();
425                 if (jc.isCancelled()) {
426                     return null;
427                 }
428                 mMainHandler.post(new Runnable() {
429                     @Override
430                     public void run() {
431                         mMenuTask = null;
432                         if (jc.isCancelled()) return;
433                         MenuExecutor.updateMenuOperation(mMenu, operation);
434                         MenuExecutor.updateMenuForPanorama(mMenu, supportCallback.mAllPanorama360,
435                                 supportCallback.mHasPanorama360);
436                         if (mSharePanoramaMenuItem != null) {
437                             mSharePanoramaMenuItem.setEnabled(true);
438                             if (supportCallback.mAllPanorama360) {
439                                 mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
440                                 mShareMenuItem.setTitle(
441                                     mActivity.getResources().getString(R.string.share_as_photo));
442                             } else {
443                                 mSharePanoramaMenuItem.setVisible(false);
444                                 mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
445                                 mShareMenuItem.setTitle(
446                                     mActivity.getResources().getString(R.string.share));
447                             }
448                             mSharePanoramaActionProvider.setShareIntent(share_panorama_intent);
449                         }
450                         if (mShareMenuItem != null) {
451                             mShareMenuItem.setEnabled(true);
452                             mShareActionProvider.setShareIntent(share_intent);
453                         }
454                     }
455                 });
456                 return null;
457             }
458         });
459     }
460 
pause()461     public void pause() {
462         if (mMenuTask != null) {
463             mMenuTask.cancel();
464             mMenuTask = null;
465         }
466         mMenuExecutor.pause();
467     }
468 
resume()469     public void resume() {
470         if (mSelectionManager.inSelectionMode()) updateSupportedOperation();
471     }
472 }
473