com.forktech.cmerge.ui.ContactsListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.forktech.cmerge.ui.ContactsListFragment.java

Source

/*
 * Copyright (C) 2013 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.forktech.cmerge.ui;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Contacts.Photo;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import com.example.android.contactslist.util.ImageLoader;
import com.example.android.contactslist.util.Utils;
import com.forktech.cmerge.BuildConfig;
import com.forktech.cmerge.R;
import com.forktech.cmerge.ui.ContactsAdapter.ContactSelectListener;

/**
 * This fragment displays a list of contacts stored in the Contacts Provider.
 * Each item in the list shows the contact's thumbnail photo and display name.
 * On devices with large screens, this fragment's UI appears as part of a
 * two-pane layout, along with the UI of {@link ContactDetailFragment}. On
 * smaller screens, this fragment's UI appears as a single pane.
 *
 * This Fragment retrieves contacts based on a search string. If the user
 * doesn't enter a search string, then the list contains all the contacts in the
 * Contacts Provider. If the user enters a search string, then the list contains
 * only those contacts whose data matches the string. The Contacts Provider
 * itself controls the matching algorithm, which is a "substring" search: if the
 * search string is a substring of any of the contacts data, then there is a
 * match.
 *
 * On newer API platforms, the search is implemented in a SearchView in the
 * ActionBar; as the user types the search string, the list automatically
 * refreshes to display results ("type to filter"). On older platforms, the user
 * must enter the full string and trigger the search. In response, the trigger
 * starts a new Activity which loads a fresh instance of this fragment. The
 * resulting UI displays the filtered list and disables the search feature to
 * prevent furthering searching.
 */
public class ContactsListFragment extends ListFragment
        implements AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor>, ContactSelectListener {

    // Defines a tag for identifying log entries
    private static final String TAG = "ContactsListFragment";

    // Bundle key for saving previously selected search result item
    private static final String STATE_PREVIOUSLY_SELECTED_KEY = "com.example.android.contactslist.ui.SELECTED_ITEM";

    private ContactsAdapter mAdapter; // The main query adapter
    private ImageLoader mImageLoader; // Handles loading the contact image in a
    // background thread
    private String mSearchTerm; // Stores the current search query term

    // Contact selected listener that allows the activity holding this fragment
    // to be notified of
    // a contact being selected
    private OnContactsInteractionListener mOnContactSelectedListener;

    // Stores the previously selected search item so that on a configuration
    // change the same item
    // can be reselected again
    private int mPreviouslySelectedSearchItem = 0;

    // Whether or not the search query has changed since the last time the
    // loader was refreshed
    private boolean mSearchQueryChanged;

    // Whether or not this fragment is showing in a two-pane layout
    private boolean mIsTwoPaneLayout;

    // Whether or not this is a search result view of this fragment, only used
    // on pre-honeycomb
    // OS versions as search results are shown in-line via Action Bar search
    // from honeycomb onward
    private boolean mIsSearchResultView = false;

    private List<Integer> mContactList;

    /**
     * Fragments require an empty constructor.
     */
    public ContactsListFragment() {
    }

    /**
     * In platform versions prior to Android 3.0, the ActionBar and SearchView
     * are not supported, and the UI gets the search string from an EditText.
     * However, the fragment doesn't allow another search when search results
     * are already showing. This would confuse the user, because the resulting
     * search would re-query the Contacts Provider instead of searching the
     * listed results. This method sets the search query and also a boolean that
     * tracks if this Fragment should be displayed as a search result view or
     * not.
     *
     * @param query
     *            The contacts search query.
     */
    public void setSearchQuery(String query) {
        if (TextUtils.isEmpty(query)) {
            mIsSearchResultView = false;
        } else {
            mSearchTerm = query;
            mAdapter.setSearchTerm(query);
            mIsSearchResultView = true;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mContactList = new ArrayList<Integer>();

        // Check if this fragment is part of a two-pane set up or a single pane
        // by reading a
        // boolean from the application resource directories. This lets allows
        // us to easily specify
        // which screen sizes should use a two-pane layout by setting this
        // boolean in the
        // corresponding resource size-qualified directory.
        mIsTwoPaneLayout = false;

        // Let this fragment contribute menu items
        setHasOptionsMenu(true);

        // Create the main contacts adapter
        mAdapter = new ContactsAdapter(getActivity(), this);

        if (savedInstanceState != null) {
            // If we're restoring state after this fragment was recreated then
            // retrieve previous search term and previously selected search
            // result.
            mSearchTerm = savedInstanceState.getString(SearchManager.QUERY);
            mAdapter.setSearchTerm(savedInstanceState.getString(SearchManager.QUERY));
            mPreviouslySelectedSearchItem = savedInstanceState.getInt(STATE_PREVIOUSLY_SELECTED_KEY, 0);
        }

        /*
         * An ImageLoader object loads and resizes an image in the background
         * and binds it to the QuickContactBadge in each item layout of the
         * ListView. ImageLoader implements memory caching for each image, which
         * substantially improves refreshes of the ListView as the user scrolls
         * through it.
         * 
         * To learn more about downloading images asynchronously and caching the
         * results, read the Android training class Displaying Bitmaps
         * Efficiently.
         * 
         * http://developer.android.com/training/displaying-bitmaps/
         */
        mImageLoader = new ImageLoader(getActivity(), getListPreferredItemHeight()) {
            @Override
            protected Bitmap processBitmap(Object data) {
                // This gets called in a background thread and passed the data
                // from
                // ImageLoader.loadImage().
                return loadContactPhotoThumbnail((String) data, getImageSize());
            }
        };

        // Set a placeholder loading image for the image loader
        mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_holo_light);

        // Add a cache to the image loader
        mImageLoader.addImageCache(getActivity().getSupportFragmentManager(), 0.1f);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the list fragment layout
        return inflater.inflate(R.layout.list_layout, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Set up ListView, assign adapter and set some listeners. The adapter
        // was previously
        // created in onCreate().
        setListAdapter(mAdapter);
        getListView().setOnItemClickListener(this);
        getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
                // Pause image loader to ensure smoother scrolling when flinging
                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
                    mImageLoader.setPauseWork(true);
                } else {
                    mImageLoader.setPauseWork(false);
                }
            }

            @Override
            public void onScroll(AbsListView absListView, int i, int i1, int i2) {
            }
        });

        if (mIsTwoPaneLayout) {
            // In a two-pane layout, set choice mode to single as there will be
            // two panes
            // when an item in the ListView is selected it should remain
            // highlighted while
            // the content shows in the second pane.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        }

        // If there's a previously selected search item from a saved state then
        // don't bother
        // initializing the loader as it will be restarted later when the query
        // is populated into
        // the action bar search view (see onQueryTextChange() in
        // onCreateOptionsMenu()).
        if (mPreviouslySelectedSearchItem == 0) {
            // Initialize the loader, and create a loader identified by
            // ContactsQuery.QUERY_ID
            getLoaderManager().initLoader(ContactsQuery.QUERY_ID, null, this);
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            // Assign callback listener which the holding activity must
            // implement. This is used
            // so that when a contact item is interacted with (selected by the
            // user) the holding
            // activity will be notified and can take further action such as
            // populating the contact
            // detail pane (if in multi-pane layout) or starting a new activity
            // with the contact
            // details (single pane layout).
            mOnContactSelectedListener = (OnContactsInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnContactsInteractionListener");
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        // In the case onPause() is called during a fling the image loader is
        // un-paused to let any remaining background work complete.
        mImageLoader.setPauseWork(false);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
        // Gets the Cursor object currently bound to the ListView
        final Cursor cursor = mAdapter.getCursor();

        // Moves to the Cursor row corresponding to the ListView item that was
        // clicked
        cursor.moveToPosition(position);

        // Creates a contact lookup Uri from contact ID and lookup_key
        final Uri uri = Contacts.getLookupUri(cursor.getLong(ContactsQuery.ID),
                cursor.getString(ContactsQuery.LOOKUP_KEY));

        // Notifies the parent activity that the user selected a contact. In a
        // two-pane layout, the
        // parent activity loads a ContactDetailFragment that displays the
        // details for the selected
        // contact. In a single-pane layout, the parent activity starts a new
        // activity that
        // displays contact details in its own Fragment.
        mOnContactSelectedListener.onContactSelected(uri);

        // If two-pane layout sets the selected item to checked so it remains
        // highlighted. In a
        // single-pane layout a new activity is started so this is not needed.
        if (mIsTwoPaneLayout) {
            getListView().setItemChecked(position, true);
        }
    }

    /**
     * Called when ListView selection is cleared, for example when search mode
     * is finished and the currently selected contact should no longer be
     * selected.
     */
    private void onSelectionCleared() {
        // Uses callback to notify activity this contains this fragment
        mOnContactSelectedListener.onSelectionCleared();

        // Clears currently checked item
        getListView().clearChoices();
    }

    // This method uses APIs from newer OS version`s than the minimum that this
    // app supports. This
    // annotation tells Android lint that they are properly guarded so they
    // won't run on older OS
    // versions and can be ignored by lint.
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

        // Inflate the menu items
        inflater.inflate(R.menu.contact_list_menu, menu);
        // Locate the search item
        MenuItem searchItem = menu.findItem(R.id.menu_search);

        // In versions prior to Android 3.0, hides the search item to prevent
        // additional
        // searches. In Android 3.0 and later, searching is done via a
        // SearchView in the ActionBar.
        // Since the search doesn't create a new Activity to do the searching,
        // the menu item
        // doesn't need to be turned off.
        if (mIsSearchResultView) {
            searchItem.setVisible(false);
        }

        // In version 3.0 and later, sets up and configures the ActionBar
        // SearchView
        if (Utils.hasHoneycomb()) {

            // Retrieves the system search manager service
            final SearchManager searchManager = (SearchManager) getActivity()
                    .getSystemService(Context.SEARCH_SERVICE);

            // Retrieves the SearchView from the search menu item
            final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);

            // Assign searchable info to SearchView
            searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));

            // Set listeners for SearchView
            searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String queryText) {
                    // Nothing needs to happen when the user submits the
                    // search string
                    return true;
                }

                @Override
                public boolean onQueryTextChange(String newText) {
                    // Called when the action bar search text has
                    // changed. Updates
                    // the search filter, and restarts the loader to do
                    // a new query
                    // using the new search string.
                    String newFilter = !TextUtils.isEmpty(newText) ? newText : null;

                    // Don't do anything if the filter is empty
                    if (mSearchTerm == null && newFilter == null) {
                        return true;
                    }

                    // Don't do anything if the new filter is the same
                    // as the current filter
                    if (mSearchTerm != null && mSearchTerm.equals(newFilter)) {
                        return true;
                    }

                    // Updates current filter to new filter
                    mSearchTerm = newFilter;
                    mAdapter.setSearchTerm(newFilter);

                    // Restarts the loader. This triggers
                    // onCreateLoader(), which builds the
                    // necessary content Uri from mSearchTerm.
                    mSearchQueryChanged = true;
                    getLoaderManager().restartLoader(ContactsQuery.QUERY_ID, null, ContactsListFragment.this);
                    return true;
                }
            });

            // if (Utils.hasICS()) {
            // // This listener added in ICS
            // searchItem.setOnActionExpandListener(new
            // MenuItem.OnActionExpandListener() {
            // @Override
            // public boolean onMenuItemActionExpand(MenuItem menuItem) {
            // // Nothing to do when the action item is expanded
            // return true;
            // }
            //
            // @Override
            // public boolean onMenuItemActionCollapse(MenuItem menuItem) {
            // // When the user collapses the SearchView the current search
            // string is
            // // cleared and the loader restarted.
            // if (!TextUtils.isEmpty(mSearchTerm)) {
            // onSelectionCleared();
            // }
            // mSearchTerm = null;
            // mAdapter.setSearchTerm(null);
            // getLoaderManager().restartLoader(
            // ContactsQuery.QUERY_ID, null, ContactsListFragment.this);
            // return true;
            // }
            // });
            // }

            if (mSearchTerm != null) {
                // If search term is already set here then this fragment is
                // being restored from a saved state and the search menu item
                // needs to be expanded and populated again.

                // Stores the search term (as it will be wiped out by
                // onQueryTextChange() when the menu item is expanded).
                final String savedSearchTerm = mSearchTerm;

                // Expands the search menu item
                if (Utils.hasICS()) {
                    searchItem.expandActionView();
                }

                // Sets the SearchView to the previous search string
                searchView.setQuery(savedSearchTerm, false);
            }
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (!TextUtils.isEmpty(mSearchTerm)) {
            // Saves the current search string
            outState.putString(SearchManager.QUERY, mSearchTerm);

            // Saves the currently selected contact
            outState.putInt(STATE_PREVIOUSLY_SELECTED_KEY, getListView().getCheckedItemPosition());
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        // Sends a request to the People app to display the create contact
        // screen
        case R.id.menu_add_contact:
            final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
            startActivity(intent);
            break;
        // For platforms earlier than Android 3.0, triggers the search activity
        case R.id.menu_search:
            if (!Utils.hasHoneycomb()) {
                getActivity().onSearchRequested();
            }
            break;
        case R.id.submit:
            mergeContacts();
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    private void mergeContacts() {
        ArrayList<Integer> list = (ArrayList<Integer>) getContacts();
        int numOfContacts = list.size();

        if (numOfContacts < 2) {
            Toast.makeText(getActivity(), "Atleast select two contacts to merge", Toast.LENGTH_SHORT).show();
            return;
        }
        mAdapter.clearChecks(true);
        for (int i = 0; i < numOfContacts - 1; i++) {
            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

            ops.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
                    .withValue(ContactsContract.AggregationExceptions.TYPE,
                            ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER)
                    .withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, getContacts().get(0))
                    .withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, getContacts().get(i + 1))
                    .build());
            // Log.e("", Arrays.deepToString(getContacts().toArray()));

            try {
                ContentProviderResult[] result = getActivity().getContentResolver()
                        .applyBatch(ContactsContract.AUTHORITY, ops);
                Log.e("",
                        "result length : " + result.length + " :: " + result[0].uri + result[0].describeContents());
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (OperationApplicationException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        // If this is the loader for finding contacts in the Contacts Provider
        // (the only one supported)
        if (id == ContactsQuery.QUERY_ID) {
            Uri contentUri;

            // There are two types of searches, one which displays all contacts
            // and
            // one which filters contacts by a search query. If mSearchTerm is
            // set
            // then a search query has been entered and the latter should be
            // used.

            if (mSearchTerm == null) {
                // Since there's no search string, use the content URI that
                // searches the entire
                // Contacts table
                contentUri = ContactsQuery.CONTENT_URI;
            } else {
                // Since there's a search string, use the special content Uri
                // that searches the
                // Contacts table. The URI consists of a base Uri and the search
                // string.
                contentUri = Uri.withAppendedPath(ContactsQuery.FILTER_URI, Uri.encode(mSearchTerm));
            }

            // Returns a new CursorLoader for querying the Contacts table. No
            // arguments are used
            // for the selection clause. The search string is either encoded
            // onto the content URI,
            // or no contacts search string is used. The other search criteria
            // are constants. See
            // the ContactsQuery interface.
            return new CursorLoader(getActivity(), contentUri, ContactsQuery.PROJECTION, ContactsQuery.SELECTION,
                    null, ContactsQuery.SORT_ORDER);
        }

        Log.e(TAG, "onCreateLoader - incorrect ID provided (" + id + ")");
        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // This swaps the new cursor into the adapter.
        if (loader.getId() == ContactsQuery.QUERY_ID) {
            mAdapter.swapCursor(data);

            // If this is a two-pane layout and there is a search query then
            // there is some additional work to do around default selected
            // search item.
            if (mIsTwoPaneLayout && !TextUtils.isEmpty(mSearchTerm) && mSearchQueryChanged) {
                // Selects the first item in results, unless this fragment has
                // been restored from a saved state (like orientation change)
                // in which case it selects the previously selected search item.
                if (data != null && data.moveToPosition(mPreviouslySelectedSearchItem)) {
                    // Creates the content Uri for the previously selected
                    // contact by appending the
                    // contact's ID to the Contacts table content Uri
                    final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI,
                            String.valueOf(data.getLong(ContactsQuery.ID)));
                    mOnContactSelectedListener.onContactSelected(uri);
                    getListView().setItemChecked(mPreviouslySelectedSearchItem, true);
                } else {
                    // No results, clear selection.
                    onSelectionCleared();
                }
                // Only restore from saved state one time. Next time fall back
                // to selecting first item. If the fragment state is saved again
                // then the currently selected item will once again be saved.
                mPreviouslySelectedSearchItem = 0;
                mSearchQueryChanged = false;
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        if (loader.getId() == ContactsQuery.QUERY_ID) {
            // When the loader is being reset, clear the cursor from the
            // adapter. This allows the
            // cursor resources to be freed.
            mAdapter.swapCursor(null);
        }
    }

    /**
     * Gets the preferred height for each item in the ListView, in pixels, after
     * accounting for screen density. ImageLoader uses this value to resize
     * thumbnail images to match the ListView item height.
     *
     * @return The preferred height in pixels, based on the current theme.
     */
    private int getListPreferredItemHeight() {
        final TypedValue typedValue = new TypedValue();

        // Resolve list item preferred height theme attribute into typedValue
        getActivity().getTheme().resolveAttribute(android.R.attr.listPreferredItemHeight, typedValue, true);

        // Create a new DisplayMetrics object
        final DisplayMetrics metrics = new android.util.DisplayMetrics();

        // Populate the DisplayMetrics
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);

        // Return theme value based on DisplayMetrics
        return (int) typedValue.getDimension(metrics);
    }

    /**
     * Decodes and scales a contact's image from a file pointed to by a Uri in
     * the contact's data, and returns the result as a Bitmap. The column that
     * contains the Uri varies according to the platform version.
     *
     * @param photoData
     *            For platforms prior to Android 3.0, provide the Contact._ID
     *            column value. For Android 3.0 and later, provide the
     *            Contact.PHOTO_THUMBNAIL_URI value.
     * @param imageSize
     *            The desired target width and height of the output image in
     *            pixels.
     * @return A Bitmap containing the contact's image, resized to fit the
     *         provided image size. If no thumbnail exists, returns null.
     */
    private Bitmap loadContactPhotoThumbnail(String photoData, int imageSize) {

        // Ensures the Fragment is still added to an activity. As this method is
        // called in a
        // background thread, there's the possibility the Fragment is no longer
        // attached and
        // added to an activity. If so, no need to spend resources loading the
        // contact photo.
        if (!isAdded() || getActivity() == null) {
            return null;
        }

        // Instantiates an AssetFileDescriptor. Given a content Uri pointing to
        // an image file, the
        // ContentResolver can return an AssetFileDescriptor for the file.
        AssetFileDescriptor afd = null;

        // This "try" block catches an Exception if the file descriptor returned
        // from the Contacts
        // Provider doesn't point to an existing file.
        try {
            Uri thumbUri;
            // If Android 3.0 or later, converts the Uri passed as a string to a
            // Uri object.
            if (Utils.hasHoneycomb()) {
                thumbUri = Uri.parse(photoData);
            } else {
                // For versions prior to Android 3.0, appends the string
                // argument to the content
                // Uri for the Contacts table.
                final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, photoData);

                // Appends the content Uri for the Contacts.Photo table to the
                // previously
                // constructed contact Uri to yield a content URI for the
                // thumbnail image
                thumbUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
            }
            // Retrieves a file descriptor from the Contacts Provider. To learn
            // more about this
            // feature, read the reference documentation for
            // ContentResolver#openAssetFileDescriptor.
            afd = getActivity().getContentResolver().openAssetFileDescriptor(thumbUri, "r");

            // Gets a FileDescriptor from the AssetFileDescriptor. A
            // BitmapFactory object can
            // decode the contents of a file pointed to by a FileDescriptor into
            // a Bitmap.
            FileDescriptor fileDescriptor = afd.getFileDescriptor();

            if (fileDescriptor != null) {
                // Decodes a Bitmap from the image pointed to by the
                // FileDescriptor, and scales it
                // to the specified width and height
                return ImageLoader.decodeSampledBitmapFromDescriptor(fileDescriptor, imageSize, imageSize);
            }
        } catch (FileNotFoundException e) {
            // If the file pointed to by the thumbnail URI doesn't exist, or the
            // file can't be
            // opened in "read" mode, ContentResolver.openAssetFileDescriptor
            // throws a
            // FileNotFoundException.
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Contact photo thumbnail not found for contact " + photoData + ": " + e.toString());
            }
        } finally {
            // If an AssetFileDescriptor was returned, try to close it
            if (afd != null) {
                try {
                    afd.close();
                } catch (IOException e) {
                    // Closing a file descriptor might cause an IOException if
                    // the file is
                    // already closed. Nothing extra is needed to handle this.
                }
            }
        }

        // If the decoding failed, returns null
        return null;
    }

    /**
     * This interface must be implemented by any activity that loads this
     * fragment. When an interaction occurs, such as touching an item from the
     * ListView, these callbacks will be invoked to communicate the event back
     * to the activity.
     */
    public interface OnContactsInteractionListener {
        /**
         * Called when a contact is selected from the ListView.
         * 
         * @param contactUri
         *            The contact Uri.
         */
        public void onContactSelected(Uri contactUri);

        /**
         * Called when the ListView selection is cleared like when a contact
         * search is taking place or is finishing.
         */
        public void onSelectionCleared();
    }

    /**
     * This interface defines constants for the Cursor and CursorLoader, based
     * on constants defined in the
     * {@link android.provider.ContactsContract.Contacts} class.
     */
    public interface ContactsQuery {

        // An identifier for the loader
        final static int QUERY_ID = 1;

        // A content URI for the Contacts table
        final static Uri CONTENT_URI = Contacts.CONTENT_URI;

        // The search/filter query Uri
        final static Uri FILTER_URI = Contacts.CONTENT_FILTER_URI;

        // The selection clause for the CursorLoader query. The search criteria
        // defined here
        // restrict results to contacts that have a display name and are linked
        // to visible groups.
        // Notice that the search on the string provided by the user is
        // implemented by appending
        // the search string to CONTENT_FILTER_URI.
        @SuppressLint("InlinedApi")
        final static String SELECTION = (Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY
                : Contacts.DISPLAY_NAME) + "<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";

        // The desired sort order for the returned Cursor. In Android 3.0 and
        // later, the primary
        // sort key allows for localization. In earlier versions. use the
        // display name as the sort
        // key.
        @SuppressLint("InlinedApi")
        final static String SORT_ORDER = Utils.hasHoneycomb() ? Contacts.SORT_KEY_PRIMARY : Contacts.DISPLAY_NAME;

        // The projection for the CursorLoader query. This is a list of columns
        // that the Contacts
        // Provider should return in the Cursor.
        @SuppressLint("InlinedApi")
        final static String[] PROJECTION = {

                // The contact's row id
                Contacts._ID,

                // A pointer to the contact that is guaranteed to be more
                // permanent than _ID. Given
                // a contact's current _ID value and LOOKUP_KEY, the Contacts
                // Provider can generate
                // a "permanent" contact URI.
                Contacts.LOOKUP_KEY,

                // In platform version 3.0 and later, the Contacts table
                // contains
                // DISPLAY_NAME_PRIMARY, which either contains the contact's
                // displayable name or
                // some other useful identifier such as an email address. This
                // column isn't
                // available in earlier versions of Android, so you must use
                // Contacts.DISPLAY_NAME
                // instead.
                Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,

                // In Android 3.0 and later, the thumbnail image is pointed to
                // by
                // PHOTO_THUMBNAIL_URI. In earlier versions, there is no direct
                // pointer; instead,
                // you generate the pointer from the contact's ID value and
                // constants defined in
                // android.provider.ContactsContract.Contacts.
                Utils.hasHoneycomb() ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID,

                // The sort order column for the returned Cursor, used by the
                // AlphabetIndexer
                SORT_ORDER, Contacts.NAME_RAW_CONTACT_ID, };

        // The query column numbers which map to each value in the projection
        final static int ID = 0;
        final static int LOOKUP_KEY = 1;
        final static int DISPLAY_NAME = 2;
        final static int PHOTO_THUMBNAIL_DATA = 3;
        final static int SORT_KEY = 4;
        final static int NAME_RAW_CONTACT_ID = 5;
    }

    @Override
    public void addContact(int contactId) {
        if (!mContactList.contains(contactId)) {
            mContactList.add(contactId);
        }
    }

    @Override
    public void removeContact(int contactId) {
        Log.e("", "removed " + contactId);
        mContactList.remove(Integer.valueOf(contactId));
    }

    @Override
    public List<Integer> getContacts() {
        return mContactList;
    }
}