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