Home | History | Annotate | Download | only in editor
      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 
     17 package com.android.contacts.editor;
     18 
     19 import com.android.contacts.R;
     20 import com.android.contacts.common.ContactPhotoManager;
     21 import com.android.contacts.common.model.ValuesDelta;
     22 import com.android.contacts.common.model.account.AccountType;
     23 
     24 import android.app.Fragment;
     25 import android.content.Context;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.os.Parcel;
     29 import android.os.Parcelable;
     30 import android.provider.ContactsContract;
     31 import android.util.DisplayMetrics;
     32 import android.view.Display;
     33 import android.view.LayoutInflater;
     34 import android.view.MenuItem;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.accessibility.AccessibilityEvent;
     38 import android.widget.AdapterView;
     39 import android.widget.BaseAdapter;
     40 import android.widget.GridView;
     41 import android.widget.ImageView;
     42 
     43 import java.util.ArrayList;
     44 
     45 /**
     46  * Displays {@link Photo}s in a grid and calls back the host when one is clicked.
     47  */
     48 public class CompactPhotoSelectionFragment extends Fragment {
     49 
     50     private static final String STATE_PHOTOS = "photos";
     51     private static final String STATE_PHOTO_MODE = "photoMode";
     52     private final int VIEW_TYPE_TAKE_PHOTO = 0;
     53     private final int VIEW_TYPE_ALL_PHOTOS = 1;
     54     private final int VIEW_TYPE_IMAGE = 2;
     55 
     56     /**
     57      * Callbacks hosts this Fragment.
     58      */
     59     public interface Listener {
     60 
     61         /**
     62          * Invoked when the user wants to change their photo.
     63          */
     64         void onPhotoSelected(Photo photo);
     65     }
     66 
     67     /**
     68      * Holds a photo {@link ValuesDelta} and {@link AccountType} information to draw
     69      * an account type icon over it.
     70      */
     71     public static final class Photo implements Parcelable {
     72 
     73         public static final Creator<Photo> CREATOR = new Creator<Photo>() {
     74 
     75             public Photo createFromParcel(Parcel in) {
     76                 return new Photo(in);
     77             }
     78 
     79             public Photo[] newArray(int size) {
     80                 return new Photo[size];
     81             }
     82         };
     83 
     84         public Photo() {
     85         }
     86 
     87         private Photo(Parcel source) {
     88             readFromParcel(source);
     89         }
     90 
     91         // From AccountType, everything we need to display the account type icon
     92         public int titleRes;
     93         public int iconRes;
     94         public String syncAdapterPackageName;
     95 
     96         public String contentDescription;
     97         public String contentDescriptionChecked; // Talkback announcement when the photo is checked
     98         public String accountType;
     99         public String accountName;
    100 
    101         public ValuesDelta valuesDelta;
    102 
    103         /**
    104          * Whether the photo is being displayed for the aggregate contact.
    105          * This may be because it is marked super primary or it is the one quick contacts picked
    106          * randomly to display because none is marked super primary.
    107          */
    108         public boolean primary;
    109 
    110         /**
    111          * Pointer back to the KindSectionDataList this photo came from.
    112          * See {@link CompactRawContactsEditorView#getPhotos}
    113          * See {@link CompactRawContactsEditorView#setPrimaryPhoto}
    114          */
    115         public int kindSectionDataListIndex = -1;
    116         public int valuesDeltaListIndex = -1;
    117 
    118         /** Newly taken or selected photo that has not yet been saved to CP2. */
    119         public Uri updatedPhotoUri;
    120 
    121         public long photoId;
    122 
    123         @Override
    124         public int describeContents() {
    125             return 0;
    126         }
    127 
    128         @Override
    129         public void writeToParcel(Parcel dest, int flags) {
    130             dest.writeInt(titleRes);
    131             dest.writeInt(iconRes);
    132             dest.writeString(syncAdapterPackageName);
    133             dest.writeParcelable(valuesDelta, flags);
    134             dest.writeInt(primary ? 1 : 0);
    135             dest.writeInt(kindSectionDataListIndex);
    136             dest.writeInt(valuesDeltaListIndex);
    137             dest.writeParcelable(updatedPhotoUri, flags);
    138             dest.writeLong(photoId);
    139         }
    140 
    141         private void readFromParcel(Parcel source) {
    142             final ClassLoader classLoader = getClass().getClassLoader();
    143             titleRes = source.readInt();
    144             iconRes = source.readInt();
    145             syncAdapterPackageName = source.readString();
    146             valuesDelta = source.readParcelable(classLoader);
    147             primary = source.readInt() == 1;
    148             kindSectionDataListIndex = source.readInt();
    149             valuesDeltaListIndex = source.readInt();
    150             updatedPhotoUri = source.readParcelable(classLoader);
    151             photoId = source.readLong();
    152         }
    153     }
    154 
    155     private final class PhotoAdapter extends BaseAdapter {
    156 
    157         private final Context mContext;
    158         private final LayoutInflater mLayoutInflater;
    159 
    160         public PhotoAdapter() {
    161             mContext = getContext();
    162             mLayoutInflater = LayoutInflater.from(mContext);
    163         }
    164 
    165         @Override
    166         public int getCount() {
    167             return mPhotos == null ? 2 : mPhotos.size() + 2;
    168         }
    169 
    170         @Override
    171         public Object getItem(int index) {
    172             return mPhotos == null ? null : mPhotos.get(index);
    173         }
    174 
    175         @Override
    176         public long getItemId(int index) {
    177             return index;
    178         }
    179 
    180         @Override
    181         public int getItemViewType(int index) {
    182             if (index == 0) {
    183                 return VIEW_TYPE_TAKE_PHOTO;
    184             } else if (index == 1) {
    185                 return VIEW_TYPE_ALL_PHOTOS;
    186             } else {
    187                 return VIEW_TYPE_IMAGE;
    188             }
    189         }
    190 
    191         @Override
    192         public View getView(int position, View convertView, ViewGroup parent) {
    193             if (mPhotos == null) return null;
    194 
    195             // when position is 0 or 1, we should make sure account_type *is not* in convertView
    196             // before reusing it.
    197             if (getItemViewType(position) == 0){
    198                 if (convertView == null || convertView.findViewById(R.id.account_type) != null) {
    199                     return mLayoutInflater.inflate(R.layout.take_a_photo_button, /* root =*/ null);
    200                 }
    201                 return convertView;
    202             }
    203 
    204             if (getItemViewType(position) == 1) {
    205                 if (convertView == null || convertView.findViewById(R.id.account_type) != null) {
    206                     return mLayoutInflater.inflate(R.layout.all_photos_button, /* root =*/ null);
    207                 }
    208                 return convertView;
    209             }
    210 
    211             // when position greater than 1, we should make sure account_type *is* in convertView
    212             // before reusing it.
    213             position -= 2;
    214 
    215             final View photoItemView;
    216             if (convertView == null || convertView.findViewById(R.id.account_type) == null) {
    217                 photoItemView = mLayoutInflater.inflate(
    218                         R.layout.compact_photo_selection_item, /* root =*/ null);
    219             } else {
    220                 photoItemView = convertView;
    221             }
    222 
    223             final Photo photo = mPhotos.get(position);
    224 
    225             // Bind the photo
    226             final ImageView imageView = (ImageView) photoItemView.findViewById(R.id.image);
    227             if (photo.updatedPhotoUri != null) {
    228                 EditorUiUtils.loadPhoto(ContactPhotoManager.getInstance(mContext),
    229                         imageView, photo.updatedPhotoUri);
    230             } else {
    231                 final Long photoFileId = EditorUiUtils.getPhotoFileId(photo.valuesDelta);
    232                 if (photoFileId != null) {
    233                     final Uri photoUri = ContactsContract.DisplayPhoto.CONTENT_URI.buildUpon()
    234                             .appendPath(photoFileId.toString()).build();
    235                     EditorUiUtils.loadPhoto(ContactPhotoManager.getInstance(mContext),
    236                             imageView, photoUri);
    237                 } else {
    238                     imageView.setImageBitmap(EditorUiUtils.getPhotoBitmap(photo.valuesDelta));
    239                 }
    240             }
    241 
    242             // Add the account type icon
    243             final ImageView accountTypeImageView = (ImageView)
    244                     photoItemView.findViewById(R.id.account_type);
    245             accountTypeImageView.setImageDrawable(AccountType.getDisplayIcon(
    246                     mContext, photo.titleRes, photo.iconRes, photo.syncAdapterPackageName));
    247 
    248             // Display a check icon over the primary photo
    249             final ImageView checkImageView = (ImageView) photoItemView.findViewById(R.id.check);
    250             checkImageView.setVisibility(photo.primary ? View.VISIBLE : View.GONE);
    251 
    252             photoItemView.setContentDescription(photo.contentDescription);
    253 
    254             return photoItemView;
    255         }
    256     }
    257 
    258     private ArrayList<Photo> mPhotos;
    259     private int mPhotoMode;
    260     private Listener mListener;
    261     private GridView mGridView;
    262 
    263     public void setListener(Listener listener) {
    264         mListener = listener;
    265     }
    266 
    267     public void setPhotos(ArrayList<Photo> photos, int photoMode) {
    268         mPhotos = photos;
    269         mPhotoMode = photoMode;
    270         mGridView.setAccessibilityDelegate(new View.AccessibilityDelegate() {});
    271     }
    272 
    273     @Override
    274     public void onCreate(Bundle savedInstanceState) {
    275         super.onCreate(savedInstanceState);
    276         if (savedInstanceState != null) {
    277             mPhotos = savedInstanceState.getParcelableArrayList(STATE_PHOTOS);
    278             mPhotoMode = savedInstanceState.getInt(STATE_PHOTO_MODE, 0);
    279         }
    280     }
    281 
    282     @Override
    283     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    284         setHasOptionsMenu(true);
    285 
    286         final PhotoAdapter photoAdapter = new PhotoAdapter();
    287 
    288         final View view = inflater.inflate(R.layout.compact_photo_selection_fragment,
    289                 container, false);
    290         mGridView = (GridView) view.findViewById(R.id.grid_view);
    291         mGridView.setAdapter(photoAdapter);
    292         mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    293             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    294                 final PhotoSourceDialogFragment.Listener listener =
    295                         (PhotoSourceDialogFragment.Listener) getActivity();
    296                 if (position == 0) {
    297                     listener.onTakePhotoChosen();
    298                 } else if (position == 1) {
    299                     listener.onPickFromGalleryChosen();
    300                 } else {
    301                     // Call the host back so it can set the new photo as primary
    302                     final Photo photo = (Photo) photoAdapter.getItem(position - 2);
    303                     if (mListener != null) {
    304                         mListener.onPhotoSelected(photo);
    305                     }
    306                     handleAccessibility(photo, position);
    307                 }
    308             }
    309         });
    310 
    311         final Display display = getActivity().getWindowManager().getDefaultDisplay();
    312         final DisplayMetrics outMetrics = new DisplayMetrics ();
    313         display.getRealMetrics(outMetrics); // real metrics include the navigation Bar
    314 
    315         final float numColumns = outMetrics.widthPixels /
    316                 getResources().getDimension(R.dimen.photo_picker_item_ideal_width);
    317         mGridView.setNumColumns(Math.round(numColumns));
    318 
    319         return view;
    320     }
    321 
    322     private void handleAccessibility(Photo photo, int position) {
    323         // Use custom AccessibilityDelegate when closing this fragment to suppress event.
    324         mGridView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
    325             @Override
    326             public boolean onRequestSendAccessibilityEvent(
    327                     ViewGroup host, View child,AccessibilityEvent event) {
    328                 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
    329                     return false;
    330                 }
    331                 return super.onRequestSendAccessibilityEvent(host, child, event);
    332             }
    333         });
    334         final ViewGroup clickedView = (ViewGroup) mGridView.getChildAt(position);
    335         clickedView.announceForAccessibility(photo.contentDescriptionChecked);
    336     }
    337 
    338     @Override
    339     public void onSaveInstanceState(Bundle outState) {
    340         outState.putParcelableArrayList(STATE_PHOTOS, mPhotos);
    341         outState.putInt(STATE_PHOTO_MODE, mPhotoMode);
    342         super.onSaveInstanceState(outState);
    343     }
    344 
    345     @Override
    346     public boolean onOptionsItemSelected(MenuItem item) {
    347         switch (item.getItemId()) {
    348             case android.R.id.home:
    349                 getActivity().onBackPressed();
    350                 return true;
    351             default:
    352                 return super.onOptionsItemSelected(item);
    353         }
    354     }
    355 
    356     @Override
    357     public Context getContext() {
    358         return getActivity();
    359     }
    360 }