Home | History | Annotate | Download | only in mediapicker
      1 /*
      2  * Copyright (C) 2015 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 package com.android.messaging.ui.mediapicker;
     17 
     18 import android.content.Context;
     19 import android.graphics.Rect;
     20 import android.net.Uri;
     21 import android.os.Parcel;
     22 import android.os.Parcelable;
     23 import android.support.v4.util.ArrayMap;
     24 import android.util.AttributeSet;
     25 import android.view.Menu;
     26 import android.view.MenuInflater;
     27 import android.view.MenuItem;
     28 import android.view.View;
     29 
     30 import com.android.messaging.R;
     31 import com.android.messaging.datamodel.binding.BindingBase;
     32 import com.android.messaging.datamodel.binding.ImmutableBindingRef;
     33 import com.android.messaging.datamodel.data.DraftMessageData;
     34 import com.android.messaging.datamodel.data.GalleryGridItemData;
     35 import com.android.messaging.datamodel.data.MessagePartData;
     36 import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
     37 import com.android.messaging.ui.PersistentInstanceState;
     38 import com.android.messaging.util.Assert;
     39 import com.android.messaging.util.ContentType;
     40 import com.android.messaging.util.LogUtil;
     41 
     42 import java.util.Iterator;
     43 import java.util.Map;
     44 
     45 /**
     46  * Shows a list of galley images from external storage in a GridView with multi-select
     47  * capabilities, and with the option to intent out to a standalone image picker.
     48  */
     49 public class GalleryGridView extends MediaPickerGridView implements
     50         GalleryGridItemView.HostInterface,
     51         PersistentInstanceState,
     52         DraftMessageDataListener {
     53     /**
     54      * Implemented by the owner of this GalleryGridView instance to communicate on image
     55      * picking and multi-image selection events.
     56      */
     57     public interface GalleryGridViewListener {
     58         void onDocumentPickerItemClicked();
     59         void onItemSelected(MessagePartData item);
     60         void onItemUnselected(MessagePartData item);
     61         void onConfirmSelection();
     62         void onUpdate();
     63     }
     64 
     65     private GalleryGridViewListener mListener;
     66 
     67     // TODO: Consider putting this into the data model object if we add more states.
     68     private final ArrayMap<Uri, MessagePartData> mSelectedImages;
     69     private boolean mIsMultiSelectMode = false;
     70     private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
     71 
     72     public GalleryGridView(final Context context, final AttributeSet attrs) {
     73         super(context, attrs);
     74         mSelectedImages = new ArrayMap<Uri, MessagePartData>();
     75     }
     76 
     77     public void setHostInterface(final GalleryGridViewListener hostInterface) {
     78         mListener = hostInterface;
     79     }
     80 
     81     public void setDraftMessageDataModel(final BindingBase<DraftMessageData> dataModel) {
     82         mDraftMessageDataModel = BindingBase.createBindingReference(dataModel);
     83         mDraftMessageDataModel.getData().addListener(this);
     84     }
     85 
     86     @Override
     87     public void onItemClicked(final View view, final GalleryGridItemData data,
     88             final boolean longClick) {
     89         if (data.isDocumentPickerItem()) {
     90             mListener.onDocumentPickerItemClicked();
     91         } else if (ContentType.isMediaType(data.getContentType())) {
     92             if (longClick) {
     93                 // Turn on multi-select mode when an item is long-pressed.
     94                 setMultiSelectEnabled(true);
     95             }
     96 
     97             final Rect startRect = new Rect();
     98             view.getGlobalVisibleRect(startRect);
     99             if (isMultiSelectEnabled()) {
    100                 toggleItemSelection(startRect, data);
    101             } else {
    102                 mListener.onItemSelected(data.constructMessagePartData(startRect));
    103             }
    104         } else {
    105             LogUtil.w(LogUtil.BUGLE_TAG,
    106                     "Selected item has invalid contentType " + data.getContentType());
    107         }
    108     }
    109 
    110     @Override
    111     public boolean isItemSelected(final GalleryGridItemData data) {
    112         return mSelectedImages.containsKey(data.getImageUri());
    113     }
    114 
    115     int getSelectionCount() {
    116         return mSelectedImages.size();
    117     }
    118 
    119     @Override
    120     public boolean isMultiSelectEnabled() {
    121         return mIsMultiSelectMode;
    122     }
    123 
    124     private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) {
    125         Assert.isTrue(isMultiSelectEnabled());
    126         if (isItemSelected(data)) {
    127             final MessagePartData item = mSelectedImages.remove(data.getImageUri());
    128             mListener.onItemUnselected(item);
    129             if (mSelectedImages.size() == 0) {
    130                 // No image is selected any more, turn off multi-select mode.
    131                 setMultiSelectEnabled(false);
    132             }
    133         } else {
    134             final MessagePartData item = data.constructMessagePartData(startRect);
    135             mSelectedImages.put(data.getImageUri(), item);
    136             mListener.onItemSelected(item);
    137         }
    138         invalidateViews();
    139     }
    140 
    141     private void toggleMultiSelect() {
    142         mIsMultiSelectMode = !mIsMultiSelectMode;
    143         invalidateViews();
    144     }
    145 
    146     private void setMultiSelectEnabled(final boolean enabled) {
    147         if (mIsMultiSelectMode != enabled) {
    148             toggleMultiSelect();
    149         }
    150     }
    151 
    152     private boolean canToggleMultiSelect() {
    153         // We allow the user to toggle multi-select mode only when nothing has selected. If
    154         // something has been selected, we show a confirm button instead.
    155         return mSelectedImages.size() == 0;
    156     }
    157 
    158     public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
    159         inflater.inflate(R.menu.gallery_picker_menu, menu);
    160         final MenuItem toggleMultiSelect = menu.findItem(R.id.action_multiselect);
    161         final MenuItem confirmMultiSelect = menu.findItem(R.id.action_confirm_multiselect);
    162         final boolean canToggleMultiSelect = canToggleMultiSelect();
    163         toggleMultiSelect.setVisible(canToggleMultiSelect);
    164         confirmMultiSelect.setVisible(!canToggleMultiSelect);
    165     }
    166 
    167     public boolean onOptionsItemSelected(final MenuItem item) {
    168         switch (item.getItemId()) {
    169             case R.id.action_multiselect:
    170                 Assert.isTrue(canToggleMultiSelect());
    171                 toggleMultiSelect();
    172                 return true;
    173 
    174             case R.id.action_confirm_multiselect:
    175                 Assert.isTrue(!canToggleMultiSelect());
    176                 mListener.onConfirmSelection();
    177                 return true;
    178         }
    179         return false;
    180     }
    181 
    182 
    183     @Override
    184     public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
    185         mDraftMessageDataModel.ensureBound(data);
    186         // Whenever attachment changed, refresh selection state to remove those that are not
    187         // selected.
    188         if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
    189                 DraftMessageData.ATTACHMENTS_CHANGED) {
    190             refreshImageSelectionStateOnAttachmentChange();
    191         }
    192     }
    193 
    194     @Override
    195     public void onDraftAttachmentLimitReached(final DraftMessageData data) {
    196         mDraftMessageDataModel.ensureBound(data);
    197         // Whenever draft attachment limit is reach, refresh selection state to remove those
    198         // not actually added to draft.
    199         refreshImageSelectionStateOnAttachmentChange();
    200     }
    201 
    202     @Override
    203     public void onDraftAttachmentLoadFailed() {
    204         // Nothing to do since the failed attachment gets removed automatically.
    205     }
    206 
    207     private void refreshImageSelectionStateOnAttachmentChange() {
    208         boolean changed = false;
    209         final Iterator<Map.Entry<Uri, MessagePartData>> iterator =
    210                 mSelectedImages.entrySet().iterator();
    211         while (iterator.hasNext()) {
    212             Map.Entry<Uri, MessagePartData> entry = iterator.next();
    213             if (!mDraftMessageDataModel.getData().containsAttachment(entry.getKey())) {
    214                 iterator.remove();
    215                 changed = true;
    216             }
    217         }
    218 
    219         if (changed) {
    220             mListener.onUpdate();
    221             invalidateViews();
    222         }
    223     }
    224 
    225     @Override   // PersistentInstanceState
    226     public Parcelable saveState() {
    227         return onSaveInstanceState();
    228     }
    229 
    230     @Override   // PersistentInstanceState
    231     public void restoreState(final Parcelable restoredState) {
    232         onRestoreInstanceState(restoredState);
    233         invalidateViews();
    234     }
    235 
    236     @Override
    237     public Parcelable onSaveInstanceState() {
    238         final Parcelable superState = super.onSaveInstanceState();
    239         final SavedState savedState = new SavedState(superState);
    240         savedState.isMultiSelectMode = mIsMultiSelectMode;
    241         savedState.selectedImages = mSelectedImages.values()
    242                 .toArray(new MessagePartData[mSelectedImages.size()]);
    243         return savedState;
    244     }
    245 
    246     @Override
    247     public void onRestoreInstanceState(final Parcelable state) {
    248         if (!(state instanceof SavedState)) {
    249             super.onRestoreInstanceState(state);
    250             return;
    251         }
    252 
    253         final SavedState savedState = (SavedState) state;
    254         super.onRestoreInstanceState(savedState.getSuperState());
    255         mIsMultiSelectMode = savedState.isMultiSelectMode;
    256         mSelectedImages.clear();
    257         for (int i = 0; i < savedState.selectedImages.length; i++) {
    258             final MessagePartData selectedImage = savedState.selectedImages[i];
    259             mSelectedImages.put(selectedImage.getContentUri(), selectedImage);
    260         }
    261     }
    262 
    263     @Override   // PersistentInstanceState
    264     public void resetState() {
    265         mSelectedImages.clear();
    266         mIsMultiSelectMode = false;
    267         invalidateViews();
    268     }
    269 
    270     public static class SavedState extends BaseSavedState {
    271         boolean isMultiSelectMode;
    272         MessagePartData[] selectedImages;
    273 
    274         SavedState(final Parcelable superState) {
    275             super(superState);
    276         }
    277 
    278         private SavedState(final Parcel in) {
    279             super(in);
    280             isMultiSelectMode = in.readInt() == 1 ? true : false;
    281 
    282             // Read parts
    283             final int partCount = in.readInt();
    284             selectedImages = new MessagePartData[partCount];
    285             for (int i = 0; i < partCount; i++) {
    286                 selectedImages[i] = ((MessagePartData) in.readParcelable(
    287                         MessagePartData.class.getClassLoader()));
    288             }
    289         }
    290 
    291         @Override
    292         public void writeToParcel(final Parcel out, final int flags) {
    293             super.writeToParcel(out, flags);
    294             out.writeInt(isMultiSelectMode ? 1 : 0);
    295 
    296             // Write parts
    297             out.writeInt(selectedImages.length);
    298             for (final MessagePartData image : selectedImages) {
    299                 out.writeParcelable(image, flags);
    300             }
    301         }
    302 
    303         public static final Parcelable.Creator<SavedState> CREATOR =
    304                 new Parcelable.Creator<SavedState>() {
    305             @Override
    306             public SavedState createFromParcel(final Parcel in) {
    307                 return new SavedState(in);
    308             }
    309             @Override
    310             public SavedState[] newArray(final int size) {
    311                 return new SavedState[size];
    312             }
    313         };
    314     }
    315 }
    316