/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.contacts.editor; import com.android.contacts.R; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import android.app.Fragment; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.provider.ContactsContract; import android.util.DisplayMetrics; import android.view.Display; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import java.util.ArrayList; /** * Displays {@link Photo}s in a grid and calls back the host when one is clicked. */ public class CompactPhotoSelectionFragment extends Fragment { private static final String STATE_PHOTOS = "photos"; private static final String STATE_PHOTO_MODE = "photoMode"; private final int VIEW_TYPE_TAKE_PHOTO = 0; private final int VIEW_TYPE_ALL_PHOTOS = 1; private final int VIEW_TYPE_IMAGE = 2; /** * Callbacks hosts this Fragment. */ public interface Listener { /** * Invoked when the user wants to change their photo. */ void onPhotoSelected(Photo photo); } /** * Holds a photo {@link ValuesDelta} and {@link AccountType} information to draw * an account type icon over it. */ public static final class Photo implements Parcelable { public static final Creator<Photo> CREATOR = new Creator<Photo>() { public Photo createFromParcel(Parcel in) { return new Photo(in); } public Photo[] newArray(int size) { return new Photo[size]; } }; public Photo() { } private Photo(Parcel source) { readFromParcel(source); } // From AccountType, everything we need to display the account type icon public int titleRes; public int iconRes; public String syncAdapterPackageName; public String contentDescription; public String contentDescriptionChecked; // Talkback announcement when the photo is checked public String accountType; public String accountName; public ValuesDelta valuesDelta; /** * Whether the photo is being displayed for the aggregate contact. * This may be because it is marked super primary or it is the one quick contacts picked * randomly to display because none is marked super primary. */ public boolean primary; /** * Pointer back to the KindSectionDataList this photo came from. * See {@link CompactRawContactsEditorView#getPhotos} * See {@link CompactRawContactsEditorView#setPrimaryPhoto} */ public int kindSectionDataListIndex = -1; public int valuesDeltaListIndex = -1; /** Newly taken or selected photo that has not yet been saved to CP2. */ public Uri updatedPhotoUri; public long photoId; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(titleRes); dest.writeInt(iconRes); dest.writeString(syncAdapterPackageName); dest.writeParcelable(valuesDelta, flags); dest.writeInt(primary ? 1 : 0); dest.writeInt(kindSectionDataListIndex); dest.writeInt(valuesDeltaListIndex); dest.writeParcelable(updatedPhotoUri, flags); dest.writeLong(photoId); } private void readFromParcel(Parcel source) { final ClassLoader classLoader = getClass().getClassLoader(); titleRes = source.readInt(); iconRes = source.readInt(); syncAdapterPackageName = source.readString(); valuesDelta = source.readParcelable(classLoader); primary = source.readInt() == 1; kindSectionDataListIndex = source.readInt(); valuesDeltaListIndex = source.readInt(); updatedPhotoUri = source.readParcelable(classLoader); photoId = source.readLong(); } } private final class PhotoAdapter extends BaseAdapter { private final Context mContext; private final LayoutInflater mLayoutInflater; public PhotoAdapter() { mContext = getContext(); mLayoutInflater = LayoutInflater.from(mContext); } @Override public int getCount() { return mPhotos == null ? 2 : mPhotos.size() + 2; } @Override public Object getItem(int index) { return mPhotos == null ? null : mPhotos.get(index); } @Override public long getItemId(int index) { return index; } @Override public int getItemViewType(int index) { if (index == 0) { return VIEW_TYPE_TAKE_PHOTO; } else if (index == 1) { return VIEW_TYPE_ALL_PHOTOS; } else { return VIEW_TYPE_IMAGE; } } @Override public View getView(int position, View convertView, ViewGroup parent) { if (mPhotos == null) return null; // when position is 0 or 1, we should make sure account_type *is not* in convertView // before reusing it. if (getItemViewType(position) == 0){ if (convertView == null || convertView.findViewById(R.id.account_type) != null) { return mLayoutInflater.inflate(R.layout.take_a_photo_button, /* root =*/ null); } return convertView; } if (getItemViewType(position) == 1) { if (convertView == null || convertView.findViewById(R.id.account_type) != null) { return mLayoutInflater.inflate(R.layout.all_photos_button, /* root =*/ null); } return convertView; } // when position greater than 1, we should make sure account_type *is* in convertView // before reusing it. position -= 2; final View photoItemView; if (convertView == null || convertView.findViewById(R.id.account_type) == null) { photoItemView = mLayoutInflater.inflate( R.layout.compact_photo_selection_item, /* root =*/ null); } else { photoItemView = convertView; } final Photo photo = mPhotos.get(position); // Bind the photo final ImageView imageView = (ImageView) photoItemView.findViewById(R.id.image); if (photo.updatedPhotoUri != null) { EditorUiUtils.loadPhoto(ContactPhotoManager.getInstance(mContext), imageView, photo.updatedPhotoUri); } else { final Long photoFileId = EditorUiUtils.getPhotoFileId(photo.valuesDelta); if (photoFileId != null) { final Uri photoUri = ContactsContract.DisplayPhoto.CONTENT_URI.buildUpon() .appendPath(photoFileId.toString()).build(); EditorUiUtils.loadPhoto(ContactPhotoManager.getInstance(mContext), imageView, photoUri); } else { imageView.setImageBitmap(EditorUiUtils.getPhotoBitmap(photo.valuesDelta)); } } // Add the account type icon final ImageView accountTypeImageView = (ImageView) photoItemView.findViewById(R.id.account_type); accountTypeImageView.setImageDrawable(AccountType.getDisplayIcon( mContext, photo.titleRes, photo.iconRes, photo.syncAdapterPackageName)); // Display a check icon over the primary photo final ImageView checkImageView = (ImageView) photoItemView.findViewById(R.id.check); checkImageView.setVisibility(photo.primary ? View.VISIBLE : View.GONE); photoItemView.setContentDescription(photo.contentDescription); return photoItemView; } } private ArrayList<Photo> mPhotos; private int mPhotoMode; private Listener mListener; private GridView mGridView; public void setListener(Listener listener) { mListener = listener; } public void setPhotos(ArrayList<Photo> photos, int photoMode) { mPhotos = photos; mPhotoMode = photoMode; mGridView.setAccessibilityDelegate(new View.AccessibilityDelegate() {}); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mPhotos = savedInstanceState.getParcelableArrayList(STATE_PHOTOS); mPhotoMode = savedInstanceState.getInt(STATE_PHOTO_MODE, 0); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { setHasOptionsMenu(true); final PhotoAdapter photoAdapter = new PhotoAdapter(); final View view = inflater.inflate(R.layout.compact_photo_selection_fragment, container, false); mGridView = (GridView) view.findViewById(R.id.grid_view); mGridView.setAdapter(photoAdapter); mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final PhotoSourceDialogFragment.Listener listener = (PhotoSourceDialogFragment.Listener) getActivity(); if (position == 0) { listener.onTakePhotoChosen(); } else if (position == 1) { listener.onPickFromGalleryChosen(); } else { // Call the host back so it can set the new photo as primary final Photo photo = (Photo) photoAdapter.getItem(position - 2); if (mListener != null) { mListener.onPhotoSelected(photo); } handleAccessibility(photo, position); } } }); final Display display = getActivity().getWindowManager().getDefaultDisplay(); final DisplayMetrics outMetrics = new DisplayMetrics (); display.getRealMetrics(outMetrics); // real metrics include the navigation Bar final float numColumns = outMetrics.widthPixels / getResources().getDimension(R.dimen.photo_picker_item_ideal_width); mGridView.setNumColumns(Math.round(numColumns)); return view; } private void handleAccessibility(Photo photo, int position) { // Use custom AccessibilityDelegate when closing this fragment to suppress event. mGridView.setAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public boolean onRequestSendAccessibilityEvent( ViewGroup host, View child,AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { return false; } return super.onRequestSendAccessibilityEvent(host, child, event); } }); final ViewGroup clickedView = (ViewGroup) mGridView.getChildAt(position); clickedView.announceForAccessibility(photo.contentDescriptionChecked); } @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelableArrayList(STATE_PHOTOS, mPhotos); outState.putInt(STATE_PHOTO_MODE, mPhotoMode); super.onSaveInstanceState(outState); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: getActivity().onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } } @Override public Context getContext() { return getActivity(); } }