Advertisement
Guest User

Untitled

a guest
Jun 8th, 2013
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 38.48 KB | None | 0 0
  1. /*
  2.  * Copyright (C) 2013 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.example.android.contactslist.ui;
  18.  
  19. import android.annotation.SuppressLint;
  20. import android.annotation.TargetApi;
  21. import android.app.Activity;
  22. import android.app.SearchManager;
  23. import android.content.Context;
  24. import android.content.Intent;
  25. import android.content.res.AssetFileDescriptor;
  26. import android.database.Cursor;
  27. import android.graphics.Bitmap;
  28. import android.net.Uri;
  29. import android.os.Build;
  30. import android.os.Bundle;
  31. import android.provider.ContactsContract.Contacts;
  32. import android.provider.ContactsContract.Contacts.Photo;
  33. import android.support.v4.app.ListFragment;
  34. import android.support.v4.app.LoaderManager;
  35. import android.support.v4.content.CursorLoader;
  36. import android.support.v4.content.Loader;
  37. import android.support.v4.widget.CursorAdapter;
  38. import android.text.SpannableString;
  39. import android.text.TextUtils;
  40. import android.text.style.TextAppearanceSpan;
  41. import android.util.DisplayMetrics;
  42. import android.util.Log;
  43. import android.util.TypedValue;
  44. import android.view.LayoutInflater;
  45. import android.view.Menu;
  46. import android.view.MenuInflater;
  47. import android.view.MenuItem;
  48. import android.view.View;
  49. import android.view.ViewGroup;
  50. import android.widget.AbsListView;
  51. import android.widget.AdapterView;
  52. import android.widget.AlphabetIndexer;
  53. import android.widget.ListView;
  54. import android.widget.QuickContactBadge;
  55. import android.widget.SearchView;
  56. import android.widget.SectionIndexer;
  57. import android.widget.TextView;
  58.  
  59. import com.example.android.contactslist.BuildConfig;
  60. import com.example.android.contactslist.R;
  61. import com.example.android.contactslist.util.ImageLoader;
  62. import com.example.android.contactslist.util.Utils;
  63.  
  64. import java.io.FileDescriptor;
  65. import java.io.FileNotFoundException;
  66. import java.io.IOException;
  67. import java.util.Locale;
  68.  
  69. /**
  70.  * This fragment displays a list of contacts stored in the Contacts Provider.
  71.  * Each item in the list shows the contact's thumbnail photo and display name.
  72.  * On devices with large screens, this fragment's UI appears as part of a
  73.  * two-pane layout, along with the UI of {@link ContactDetailFragment}. On
  74.  * smaller screens, this fragment's UI appears as a single pane.
  75.  *
  76.  * This Fragment retrieves contacts based on a search string. If the user
  77.  * doesn't enter a search string, then the list contains all the contacts in the
  78.  * Contacts Provider. If the user enters a search string, then the list contains
  79.  * only those contacts whose data matches the string. The Contacts Provider
  80.  * itself controls the matching algorithm, which is a "substring" search: if the
  81.  * search string is a substring of any of the contacts data, then there is a
  82.  * match.
  83.  *
  84.  * On newer API platforms, the search is implemented in a SearchView in the
  85.  * ActionBar; as the user types the search string, the list automatically
  86.  * refreshes to display results ("type to filter"). On older platforms, the user
  87.  * must enter the full string and trigger the search. In response, the trigger
  88.  * starts a new Activity which loads a fresh instance of this fragment. The
  89.  * resulting UI displays the filtered list and disables the search feature to
  90.  * prevent furthering searching.
  91.  */
  92. public class ContactsListFragment extends ListFragment implements
  93.         AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor>
  94. {
  95.  
  96.     // Defines a tag for identifying log entries
  97.     private static final String TAG = "ContactsListFragment";
  98.  
  99.     // Bundle key for saving previously selected search result item
  100.     private static final String STATE_PREVIOUSLY_SELECTED_KEY = "com.example.android.contactslist.ui.SELECTED_ITEM";
  101.  
  102.     private ContactsAdapter mAdapter; // The main query adapter
  103.     private ImageLoader mImageLoader; // Handles loading the contact image in a
  104.                                         // background thread
  105.     private String mSearchTerm; // Stores the current search query term
  106.  
  107.     // Contact selected listener that allows the activity holding this fragment
  108.     // to be notified of
  109.     // a contact being selected
  110.     private OnContactsInteractionListener mOnContactSelectedListener;
  111.  
  112.     // Stores the previously selected search item so that on a configuration
  113.     // change the same item
  114.     // can be reselected again
  115.     private int mPreviouslySelectedSearchItem = 0;
  116.  
  117.     // Whether or not the search query has changed since the last time the
  118.     // loader was refreshed
  119.     private boolean mSearchQueryChanged;
  120.  
  121.     // Whether or not this fragment is showing in a two-pane layout
  122.     private boolean mIsTwoPaneLayout;
  123.  
  124.     // Whether or not this is a search result view of this fragment, only used
  125.     // on pre-honeycomb
  126.     // OS versions as search results are shown in-line via Action Bar search
  127.     // from honeycomb onward
  128.     private boolean mIsSearchResultView = false;
  129.  
  130.     /**
  131.      * Fragments require an empty constructor.
  132.      */
  133.     public ContactsListFragment()
  134.     {
  135.     }
  136.  
  137.     /**
  138.      * In platform versions prior to Android 3.0, the ActionBar and SearchView
  139.      * are not supported, and the UI gets the search string from an EditText.
  140.      * However, the fragment doesn't allow another search when search results
  141.      * are already showing. This would confuse the user, because the resulting
  142.      * search would re-query the Contacts Provider instead of searching the
  143.      * listed results. This method sets the search query and also a boolean that
  144.      * tracks if this Fragment should be displayed as a search result view or
  145.      * not.
  146.      *
  147.      * @param query
  148.      *            The contacts search query.
  149.      */
  150.     public void setSearchQuery(String query)
  151.     {
  152.         if (TextUtils.isEmpty(query))
  153.         {
  154.             mIsSearchResultView = false;
  155.         } else
  156.         {
  157.             mSearchTerm = query;
  158.             mIsSearchResultView = true;
  159.         }
  160.     }
  161.  
  162.     @Override
  163.     public void onCreate(Bundle savedInstanceState)
  164.     {
  165.         super.onCreate(savedInstanceState);
  166.  
  167.         // Check if this fragment is part of a two-pane set up or a single pane
  168.         // by reading a
  169.         // boolean from the application resource directories. This lets allows
  170.         // us to easily specify
  171.         // which screen sizes should use a two-pane layout by setting this
  172.         // boolean in the
  173.         // corresponding resource size-qualified directory.
  174.         mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
  175.  
  176.         // Let this fragment contribute menu items
  177.         setHasOptionsMenu(true);
  178.  
  179.         // Create the main contacts adapter
  180.         mAdapter = new ContactsAdapter(getActivity());
  181.  
  182.         if (savedInstanceState != null)
  183.         {
  184.             // If we're restoring state after this fragment was recreated then
  185.             // retrieve previous search term and previously selected search
  186.             // result.
  187.             mSearchTerm = savedInstanceState.getString(SearchManager.QUERY);
  188.             mPreviouslySelectedSearchItem = savedInstanceState.getInt(
  189.                     STATE_PREVIOUSLY_SELECTED_KEY, 0);
  190.         }
  191.  
  192.         /*
  193.          * An ImageLoader object loads and resizes an image in the background
  194.          * and binds it to the QuickContactBadge in each item layout of the
  195.          * ListView. ImageLoader implements memory caching for each image, which
  196.          * substantially improves refreshes of the ListView as the user scrolls
  197.          * through it.
  198.          *
  199.          * To learn more about downloading images asynchronously and caching the
  200.          * results, read the Android training class Displaying Bitmaps
  201.          * Efficiently.
  202.          *
  203.          * http://developer.android.com/training/displaying-bitmaps/
  204.          */
  205.         mImageLoader = new ImageLoader(getActivity(),
  206.                 getListPreferredItemHeight())
  207.         {
  208.             @Override
  209.             protected Bitmap processBitmap(Object data)
  210.             {
  211.                 // This gets called in a background thread and passed the data
  212.                 // from
  213.                 // ImageLoader.loadImage().
  214.                 return loadContactPhotoThumbnail((String) data, getImageSize());
  215.             }
  216.         };
  217.  
  218.         // Set a placeholder loading image for the image loader
  219.         mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_holo_light);
  220.  
  221.         // Add a cache to the image loader
  222.         mImageLoader.addImageCache(getActivity().getSupportFragmentManager(),
  223.                 0.1f);
  224.     }
  225.  
  226.     @Override
  227.     public View onCreateView(LayoutInflater inflater, ViewGroup container,
  228.             Bundle savedInstanceState)
  229.     {
  230.         // Inflate the list fragment layout
  231.         return inflater.inflate(R.layout.contact_list_fragment, container,
  232.                 false);
  233.     }
  234.  
  235.     @Override
  236.     public void onActivityCreated(Bundle savedInstanceState)
  237.     {
  238.         super.onActivityCreated(savedInstanceState);
  239.  
  240.         // Set up ListView, assign adapter and set some listeners. The adapter
  241.         // was previously
  242.         // created in onCreate().
  243.         setListAdapter(mAdapter);
  244.         getListView().setOnItemClickListener(this);
  245.         getListView().setOnScrollListener(new AbsListView.OnScrollListener()
  246.         {
  247.             @Override
  248.             public void onScrollStateChanged(AbsListView absListView,
  249.                     int scrollState)
  250.             {
  251.                 // Pause image loader to ensure smoother scrolling when flinging
  252.                 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING)
  253.                 {
  254.                     mImageLoader.setPauseWork(true);
  255.                 } else
  256.                 {
  257.                     mImageLoader.setPauseWork(false);
  258.                 }
  259.             }
  260.  
  261.             @Override
  262.             public void onScroll(AbsListView absListView, int i, int i1, int i2)
  263.             {
  264.             }
  265.         });
  266.  
  267.         if (mIsTwoPaneLayout)
  268.         {
  269.             // In a two-pane layout, set choice mode to single as there will be
  270.             // two panes
  271.             // when an item in the ListView is selected it should remain
  272.             // highlighted while
  273.             // the content shows in the second pane.
  274.             getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
  275.         }
  276.  
  277.         // If there's a previously selected search item from a saved state then
  278.         // don't bother
  279.         // initializing the loader as it will be restarted later when the query
  280.         // is populated into
  281.         // the action bar search view (see onQueryTextChange() in
  282.         // onCreateOptionsMenu()).
  283.         if (mPreviouslySelectedSearchItem == 0)
  284.         {
  285.             // Initialize the loader, and create a loader identified by
  286.             // ContactsQuery.QUERY_ID
  287.             getLoaderManager().initLoader(ContactsQuery.QUERY_ID, null, this);
  288.         }
  289.     }
  290.  
  291.     @Override
  292.     public void onAttach(Activity activity)
  293.     {
  294.         super.onAttach(activity);
  295.  
  296.         try
  297.         {
  298.             // Assign callback listener which the holding activity must
  299.             // implement. This is used
  300.             // so that when a contact item is interacted with (selected by the
  301.             // user) the holding
  302.             // activity will be notified and can take further action such as
  303.             // populating the contact
  304.             // detail pane (if in multi-pane layout) or starting a new activity
  305.             // with the contact
  306.             // details (single pane layout).
  307.             mOnContactSelectedListener = (OnContactsInteractionListener) activity;
  308.         } catch (ClassCastException e)
  309.         {
  310.             throw new ClassCastException(activity.toString()
  311.                     + " must implement OnContactsInteractionListener");
  312.         }
  313.     }
  314.  
  315.     @Override
  316.     public void onPause()
  317.     {
  318.         super.onPause();
  319.  
  320.         // In the case onPause() is called during a fling the image loader is
  321.         // un-paused to let any remaining background work complete.
  322.         mImageLoader.setPauseWork(false);
  323.     }
  324.  
  325.     @Override
  326.     public void onItemClick(AdapterView<?> parent, View v, int position, long id)
  327.     {
  328.         // Gets the Cursor object currently bound to the ListView
  329.         final Cursor cursor = mAdapter.getCursor();
  330.  
  331.         // Moves to the Cursor row corresponding to the ListView item that was
  332.         // clicked
  333.         cursor.moveToPosition(position);
  334.  
  335.         // Creates a contact lookup Uri from contact ID and lookup_key
  336.         final Uri uri = Contacts.getLookupUri(cursor.getLong(ContactsQuery.ID),
  337.                 cursor.getString(ContactsQuery.LOOKUP_KEY));
  338.  
  339.         // Notifies the parent activity that the user selected a contact. In a
  340.         // two-pane layout, the
  341.         // parent activity loads a ContactDetailFragment that displays the
  342.         // details for the selected
  343.         // contact. In a single-pane layout, the parent activity starts a new
  344.         // activity that
  345.         // displays contact details in its own Fragment.
  346.         mOnContactSelectedListener.onContactSelected(uri);
  347.  
  348.         // If two-pane layout sets the selected item to checked so it remains
  349.         // highlighted. In a
  350.         // single-pane layout a new activity is started so this is not needed.
  351.         if (mIsTwoPaneLayout)
  352.         {
  353.             getListView().setItemChecked(position, true);
  354.         }
  355.     }
  356.  
  357.     /**
  358.      * Called when ListView selection is cleared, for example when search mode
  359.      * is finished and the currently selected contact should no longer be
  360.      * selected.
  361.      */
  362.     private void onSelectionCleared()
  363.     {
  364.         // Uses callback to notify activity this contains this fragment
  365.         mOnContactSelectedListener.onSelectionCleared();
  366.  
  367.         // Clears currently checked item
  368.         getListView().clearChoices();
  369.     }
  370.  
  371.     // This method uses APIs from newer OS versions than the minimum that this
  372.     // app supports. This
  373.     // annotation tells Android lint that they are properly guarded so they
  374.     // won't run on older OS
  375.     // versions and can be ignored by lint.
  376.     @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  377.     @Override
  378.     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
  379.     {
  380.  
  381.         // Inflate the menu items
  382.         inflater.inflate(R.menu.contact_list_menu, menu);
  383.         // Locate the search item
  384.         MenuItem searchItem = menu.findItem(R.id.menu_search);
  385.  
  386.         // In versions prior to Android 3.0, hides the search item to prevent
  387.         // additional
  388.         // searches. In Android 3.0 and later, searching is done via a
  389.         // SearchView in the ActionBar.
  390.         // Since the search doesn't create a new Activity to do the searching,
  391.         // the menu item
  392.         // doesn't need to be turned off.
  393.         if (mIsSearchResultView)
  394.         {
  395.             searchItem.setVisible(false);
  396.         }
  397.  
  398.         // In version 3.0 and later, sets up and configures the ActionBar
  399.         // SearchView
  400.         if (Utils.hasHoneycomb())
  401.         {
  402.  
  403.             // Retrieves the system search manager service
  404.             final SearchManager searchManager = (SearchManager) getActivity()
  405.                     .getSystemService(Context.SEARCH_SERVICE);
  406.  
  407.             // Retrieves the SearchView from the search menu item
  408.             final SearchView searchView = (SearchView) searchItem
  409.                     .getActionView();
  410.  
  411.             // Assign searchable info to SearchView
  412.             searchView.setSearchableInfo(searchManager
  413.                     .getSearchableInfo(getActivity().getComponentName()));
  414.  
  415.             // Set listeners for SearchView
  416.             searchView
  417.                     .setOnQueryTextListener(new SearchView.OnQueryTextListener()
  418.                     {
  419.                         @Override
  420.                         public boolean onQueryTextSubmit(String queryText)
  421.                         {
  422.                             // Nothing needs to happen when the user submits the
  423.                             // search string
  424.                             return true;
  425.                         }
  426.  
  427.                         @Override
  428.                         public boolean onQueryTextChange(String newText)
  429.                         {
  430.                             // Called when the action bar search text has
  431.                             // changed. Updates
  432.                             // the search filter, and restarts the loader to do
  433.                             // a new query
  434.                             // using the new search string.
  435.                             String newFilter = !TextUtils.isEmpty(newText) ? newText
  436.                                     : null;
  437.  
  438.                             // Don't do anything if the filter is empty
  439.                             if (mSearchTerm == null && newFilter == null)
  440.                             {
  441.                                 return true;
  442.                             }
  443.  
  444.                             // Don't do anything if the new filter is the same
  445.                             // as the current filter
  446.                             if (mSearchTerm != null
  447.                                     && mSearchTerm.equals(newFilter))
  448.                             {
  449.                                 return true;
  450.                             }
  451.  
  452.                             // Updates current filter to new filter
  453.                             mSearchTerm = newFilter;
  454.  
  455.                             // Restarts the loader. This triggers
  456.                             // onCreateLoader(), which builds the
  457.                             // necessary content Uri from mSearchTerm.
  458.                             mSearchQueryChanged = true;
  459.                             getLoaderManager().restartLoader(
  460.                                     ContactsQuery.QUERY_ID, null,
  461.                                     ContactsListFragment.this);
  462.                             return true;
  463.                         }
  464.                     });
  465.  
  466.             if (Utils.hasICS())
  467.             {
  468.                 // This listener added in ICS
  469.                 searchItem
  470.                         .setOnActionExpandListener(new MenuItem.OnActionExpandListener()
  471.                         {
  472.                             @Override
  473.                             public boolean onMenuItemActionExpand(
  474.                                     MenuItem menuItem)
  475.                             {
  476.                                 // Nothing to do when the action item is
  477.                                 // expanded
  478.                                 return true;
  479.                             }
  480.  
  481.                             @Override
  482.                             public boolean onMenuItemActionCollapse(
  483.                                     MenuItem menuItem)
  484.                             {
  485.                                 // When the user collapses the SearchView the
  486.                                 // current search string is
  487.                                 // cleared and the loader restarted.
  488.                                 if (!TextUtils.isEmpty(mSearchTerm))
  489.                                 {
  490.                                     onSelectionCleared();
  491.                                 }
  492.                                 mSearchTerm = null;
  493.                                 getLoaderManager().restartLoader(
  494.                                         ContactsQuery.QUERY_ID, null,
  495.                                         ContactsListFragment.this);
  496.                                 return true;
  497.                             }
  498.                         });
  499.             }
  500.  
  501.             if (mSearchTerm != null)
  502.             {
  503.                 // If search term is already set here then this fragment is
  504.                 // being restored from a saved state and the search menu item
  505.                 // needs to be expanded and populated again.
  506.  
  507.                 // Stores the search term (as it will be wiped out by
  508.                 // onQueryTextChange() when the menu item is expanded).
  509.                 final String savedSearchTerm = mSearchTerm;
  510.  
  511.                 // Expands the search menu item
  512.                 if (Utils.hasICS())
  513.                 {
  514.                     searchItem.expandActionView();
  515.                 }
  516.  
  517.                 // Sets the SearchView to the previous search string
  518.                 searchView.setQuery(savedSearchTerm, false);
  519.             }
  520.         }
  521.     }
  522.  
  523.     @Override
  524.     public void onSaveInstanceState(Bundle outState)
  525.     {
  526.         super.onSaveInstanceState(outState);
  527.         if (!TextUtils.isEmpty(mSearchTerm))
  528.         {
  529.             // Saves the current search string
  530.             outState.putString(SearchManager.QUERY, mSearchTerm);
  531.  
  532.             // Saves the currently selected contact
  533.             outState.putInt(STATE_PREVIOUSLY_SELECTED_KEY, getListView()
  534.                     .getCheckedItemPosition());
  535.         }
  536.     }
  537.  
  538.     @Override
  539.     public boolean onOptionsItemSelected(MenuItem item)
  540.     {
  541.         switch (item.getItemId())
  542.         {
  543.         // Sends a request to the People app to display the create contact
  544.         // screen
  545.         case R.id.menu_add_contact:
  546.             final Intent intent = new Intent(Intent.ACTION_INSERT,
  547.                     Contacts.CONTENT_URI);
  548.             startActivity(intent);
  549.             break;
  550.         // For platforms earlier than Android 3.0, triggers the search activity
  551.         case R.id.menu_search:
  552.             if (!Utils.hasHoneycomb())
  553.             {
  554.                 getActivity().onSearchRequested();
  555.             }
  556.             break;
  557.         }
  558.         return super.onOptionsItemSelected(item);
  559.     }
  560.  
  561.     @Override
  562.     public Loader<Cursor> onCreateLoader(int id, Bundle args)
  563.     {
  564.  
  565.         // If this is the loader for finding contacts in the Contacts Provider
  566.         // (the only one supported)
  567.         if (id == ContactsQuery.QUERY_ID)
  568.         {
  569.             Uri contentUri;
  570.  
  571.             // There are two types of searches, one which displays all contacts
  572.             // and
  573.             // one which filters contacts by a search query. If mSearchTerm is
  574.             // set
  575.             // then a search query has been entered and the latter should be
  576.             // used.
  577.  
  578.             if (mSearchTerm == null)
  579.             {
  580.                 // Since there's no search string, use the content URI that
  581.                 // searches the entire
  582.                 // Contacts table
  583.                 contentUri = ContactsQuery.CONTENT_URI;
  584.             } else
  585.             {
  586.                 // Since there's a search string, use the special content Uri
  587.                 // that searches the
  588.                 // Contacts table. The URI consists of a base Uri and the search
  589.                 // string.
  590.                 contentUri = Uri.withAppendedPath(ContactsQuery.FILTER_URI,
  591.                         Uri.encode(mSearchTerm));
  592.             }
  593.  
  594.             // Returns a new CursorLoader for querying the Contacts table. No
  595.             // arguments are used
  596.             // for the selection clause. The search string is either encoded
  597.             // onto the content URI,
  598.             // or no contacts search string is used. The other search criteria
  599.             // are constants. See
  600.             // the ContactsQuery interface.
  601.             return new CursorLoader(getActivity(), contentUri,
  602.                     ContactsQuery.PROJECTION, ContactsQuery.SELECTION, null,
  603.                     ContactsQuery.SORT_ORDER);
  604.         }
  605.  
  606.         Log.e(TAG, "onCreateLoader - incorrect ID provided (" + id + ")");
  607.         return null;
  608.     }
  609.  
  610.     @Override
  611.     public void onLoadFinished(Loader<Cursor> loader, Cursor data)
  612.     {
  613.         // This swaps the new cursor into the adapter.
  614.         if (loader.getId() == ContactsQuery.QUERY_ID)
  615.         {
  616.             mAdapter.swapCursor(data);
  617.  
  618.             // If this is a two-pane layout and there is a search query then
  619.             // there is some additional work to do around default selected
  620.             // search item.
  621.             if (mIsTwoPaneLayout && !TextUtils.isEmpty(mSearchTerm)
  622.                     && mSearchQueryChanged)
  623.             {
  624.                 // Selects the first item in results, unless this fragment has
  625.                 // been restored from a saved state (like orientation change)
  626.                 // in which case it selects the previously selected search item.
  627.                 if (data != null
  628.                         && data.moveToPosition(mPreviouslySelectedSearchItem))
  629.                 {
  630.                     // Creates the content Uri for the previously selected
  631.                     // contact by appending the
  632.                     // contact's ID to the Contacts table content Uri
  633.                     final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI,
  634.                             String.valueOf(data.getLong(ContactsQuery.ID)));
  635.                     mOnContactSelectedListener.onContactSelected(uri);
  636.                     getListView().setItemChecked(mPreviouslySelectedSearchItem,
  637.                             true);
  638.                 } else
  639.                 {
  640.                     // No results, clear selection.
  641.                     onSelectionCleared();
  642.                 }
  643.                 // Only restore from saved state one time. Next time fall back
  644.                 // to selecting first item. If the fragment state is saved again
  645.                 // then the currently selected item will once again be saved.
  646.                 mPreviouslySelectedSearchItem = 0;
  647.                 mSearchQueryChanged = false;
  648.             }
  649.         }
  650.     }
  651.  
  652.     @Override
  653.     public void onLoaderReset(Loader<Cursor> loader)
  654.     {
  655.         if (loader.getId() == ContactsQuery.QUERY_ID)
  656.         {
  657.             // When the loader is being reset, clear the cursor from the
  658.             // adapter. This allows the
  659.             // cursor resources to be freed.
  660.             mAdapter.swapCursor(null);
  661.         }
  662.     }
  663.  
  664.     /**
  665.      * Gets the preferred height for each item in the ListView, in pixels, after
  666.      * accounting for screen density. ImageLoader uses this value to resize
  667.      * thumbnail images to match the ListView item height.
  668.      *
  669.      * @return The preferred height in pixels, based on the current theme.
  670.      */
  671.     private int getListPreferredItemHeight()
  672.     {
  673.         final TypedValue typedValue = new TypedValue();
  674.  
  675.         // Resolve list item preferred height theme attribute into typedValue
  676.         getActivity().getTheme().resolveAttribute(
  677.                 android.R.attr.listPreferredItemHeight, typedValue, true);
  678.  
  679.         // Create a new DisplayMetrics object
  680.         final DisplayMetrics metrics = new android.util.DisplayMetrics();
  681.  
  682.         // Populate the DisplayMetrics
  683.         getActivity().getWindowManager().getDefaultDisplay()
  684.                 .getMetrics(metrics);
  685.  
  686.         // Return theme value based on DisplayMetrics
  687.         return (int) typedValue.getDimension(metrics);
  688.     }
  689.  
  690.     /**
  691.      * Decodes and scales a contact's image from a file pointed to by a Uri in
  692.      * the contact's data, and returns the result as a Bitmap. The column that
  693.      * contains the Uri varies according to the platform version.
  694.      *
  695.      * @param photoData
  696.      *            For platforms prior to Android 3.0, provide the Contact._ID
  697.      *            column value. For Android 3.0 and later, provide the
  698.      *            Contact.PHOTO_THUMBNAIL_URI value.
  699.      * @param imageSize
  700.      *            The desired target width and height of the output image in
  701.      *            pixels.
  702.      * @return A Bitmap containing the contact's image, resized to fit the
  703.      *         provided image size. If no thumbnail exists, returns null.
  704.      */
  705.     private Bitmap loadContactPhotoThumbnail(String photoData, int imageSize)
  706.     {
  707.  
  708.         // Ensures the Fragment is still added to an activity. As this method is
  709.         // called in a
  710.         // background thread, there's the possibility the Fragment is no longer
  711.         // attached and
  712.         // added to an activity. If so, no need to spend resources loading the
  713.         // contact photo.
  714.         if (!isAdded() || getActivity() == null)
  715.         {
  716.             return null;
  717.         }
  718.  
  719.         // Instantiates an AssetFileDescriptor. Given a content Uri pointing to
  720.         // an image file, the
  721.         // ContentResolver can return an AssetFileDescriptor for the file.
  722.         AssetFileDescriptor afd = null;
  723.  
  724.         // This "try" block catches an Exception if the file descriptor returned
  725.         // from the Contacts
  726.         // Provider doesn't point to an existing file.
  727.         try
  728.         {
  729.             Uri thumbUri;
  730.             // If Android 3.0 or later, converts the Uri passed as a string to a
  731.             // Uri object.
  732.             if (Utils.hasHoneycomb())
  733.             {
  734.                 thumbUri = Uri.parse(photoData);
  735.             } else
  736.             {
  737.                 // For versions prior to Android 3.0, appends the string
  738.                 // argument to the content
  739.                 // Uri for the Contacts table.
  740.                 final Uri contactUri = Uri.withAppendedPath(
  741.                         Contacts.CONTENT_URI, photoData);
  742.  
  743.                 // Appends the content Uri for the Contacts.Photo table to the
  744.                 // previously
  745.                 // constructed contact Uri to yield a content URI for the
  746.                 // thumbnail image
  747.                 thumbUri = Uri.withAppendedPath(contactUri,
  748.                         Photo.CONTENT_DIRECTORY);
  749.             }
  750.             // Retrieves a file descriptor from the Contacts Provider. To learn
  751.             // more about this
  752.             // feature, read the reference documentation for
  753.             // ContentResolver#openAssetFileDescriptor.
  754.             afd = getActivity().getContentResolver().openAssetFileDescriptor(
  755.                     thumbUri, "r");
  756.  
  757.             // Gets a FileDescriptor from the AssetFileDescriptor. A
  758.             // BitmapFactory object can
  759.             // decode the contents of a file pointed to by a FileDescriptor into
  760.             // a Bitmap.
  761.             FileDescriptor fileDescriptor = afd.getFileDescriptor();
  762.  
  763.             if (fileDescriptor != null)
  764.             {
  765.                 // Decodes a Bitmap from the image pointed to by the
  766.                 // FileDescriptor, and scales it
  767.                 // to the specified width and height
  768.                 return ImageLoader.decodeSampledBitmapFromDescriptor(
  769.                         fileDescriptor, imageSize, imageSize);
  770.             }
  771.         } catch (FileNotFoundException e)
  772.         {
  773.             // If the file pointed to by the thumbnail URI doesn't exist, or the
  774.             // file can't be
  775.             // opened in "read" mode, ContentResolver.openAssetFileDescriptor
  776.             // throws a
  777.             // FileNotFoundException.
  778.             if (BuildConfig.DEBUG)
  779.             {
  780.                 Log.d(TAG, "Contact photo thumbnail not found for contact "
  781.                         + photoData + ": " + e.toString());
  782.             }
  783.         } finally
  784.         {
  785.             // If an AssetFileDescriptor was returned, try to close it
  786.             if (afd != null)
  787.             {
  788.                 try
  789.                 {
  790.                     afd.close();
  791.                 } catch (IOException e)
  792.                 {
  793.                     // Closing a file descriptor might cause an IOException if
  794.                     // the file is
  795.                     // already closed. Nothing extra is needed to handle this.
  796.                 }
  797.             }
  798.         }
  799.  
  800.         // If the decoding failed, returns null
  801.         return null;
  802.     }
  803.  
  804.     /**
  805.      * This is a subclass of CursorAdapter that supports binding Cursor columns
  806.      * to a view layout. If those items are part of search results, the search
  807.      * string is marked by highlighting the query text. An
  808.      * {@link AlphabetIndexer} is used to allow quicker navigation up and down
  809.      * the ListView.
  810.      */
  811.     private class ContactsAdapter extends CursorAdapter implements
  812.             SectionIndexer
  813.     {
  814.         private LayoutInflater mInflater; // Stores the layout inflater
  815.         private AlphabetIndexer mAlphabetIndexer; // Stores the AlphabetIndexer
  816.                                                     // instance
  817.         private TextAppearanceSpan highlightTextSpan; // Stores the highlight
  818.                                                         // text appearance style
  819.  
  820.         /**
  821.          * Instantiates a new Contacts Adapter.
  822.          *
  823.          * @param context
  824.          *            A context that has access to the app's layout.
  825.          */
  826.         public ContactsAdapter(Context context)
  827.         {
  828.             super(context, null, 0);
  829.  
  830.             // Stores inflater for use later
  831.             mInflater = LayoutInflater.from(context);
  832.  
  833.             // Loads a string containing the English alphabet. To fully localize
  834.             // the app, provide a
  835.             // strings.xml file in res/values-<x> directories, where <x> is a
  836.             // locale. In the file,
  837.             // define a string with android:name="alphabet" and contents set to
  838.             // all of the
  839.             // alphabetic characters in the language in their proper sort order,
  840.             // in upper case if
  841.             // applicable.
  842.             final String alphabet = context.getString(R.string.alphabet);
  843.  
  844.             // Instantiates a new AlphabetIndexer bound to the column used to
  845.             // sort contact names.
  846.             // The cursor is left null, because it has not yet been retrieved.
  847.             mAlphabetIndexer = new AlphabetIndexer(null,
  848.                     ContactsQuery.SORT_KEY, alphabet);
  849.  
  850.             // Defines a span for highlighting the part of a display name that
  851.             // matches the search
  852.             // string
  853.             highlightTextSpan = new TextAppearanceSpan(getActivity(),
  854.                     R.style.searchTextHiglight);
  855.         }
  856.  
  857.         /**
  858.          * Identifies the start of the search string in the display name column
  859.          * of a Cursor row. E.g. If displayName was "Adam" and search query
  860.          * (mSearchTerm) was "da" this would return 1.
  861.          *
  862.          * @param displayName
  863.          *            The contact display name.
  864.          * @return The starting position of the search string in the display
  865.          *         name, 0-based. The method returns -1 if the string is not
  866.          *         found in the display name, or if the search string is empty
  867.          *         or null.
  868.          */
  869.         private int indexOfSearchQuery(String displayName)
  870.         {
  871.             if (!TextUtils.isEmpty(mSearchTerm))
  872.             {
  873.                 return displayName.toLowerCase(Locale.getDefault()).indexOf(
  874.                         mSearchTerm.toLowerCase(Locale.getDefault()));
  875.             }
  876.             return -1;
  877.         }
  878.  
  879.         /**
  880.          * Overrides newView() to inflate the list item views.
  881.          */
  882.         @Override
  883.         public View newView(Context context, Cursor cursor, ViewGroup viewGroup)
  884.         {
  885.             // Inflates the list item layout.
  886.             final View itemLayout = mInflater.inflate(
  887.                     R.layout.contact_list_item, viewGroup, false);
  888.  
  889.             // Creates a new ViewHolder in which to store handles to each view
  890.             // resource. This
  891.             // allows bindView() to retrieve stored references instead of
  892.             // calling findViewById for
  893.             // each instance of the layout.
  894.             final ViewHolder holder = new ViewHolder();
  895.             holder.text1 = (TextView) itemLayout
  896.                     .findViewById(android.R.id.text1);
  897.             holder.text2 = (TextView) itemLayout
  898.                     .findViewById(android.R.id.text2);
  899.             holder.icon = (QuickContactBadge) itemLayout
  900.                     .findViewById(android.R.id.icon);
  901.  
  902.             // Stores the resourceHolder instance in itemLayout. This makes
  903.             // resourceHolder
  904.             // available to bindView and other methods that receive a handle to
  905.             // the item view.
  906.             itemLayout.setTag(holder);
  907.  
  908.             // Returns the item layout view
  909.             return itemLayout;
  910.         }
  911.  
  912.         /**
  913.          * Binds data from the Cursor to the provided view.
  914.          */
  915.         @Override
  916.         public void bindView(View view, Context context, Cursor cursor)
  917.         {
  918.             // Gets handles to individual view resources
  919.             final ViewHolder holder = (ViewHolder) view.getTag();
  920.  
  921.             // For Android 3.0 and later, gets the thumbnail image Uri from the
  922.             // current Cursor row.
  923.             // For platforms earlier than 3.0, this isn't necessary, because the
  924.             // thumbnail is
  925.             // generated from the other fields in the row.
  926.             final String photoUri = cursor
  927.                     .getString(ContactsQuery.PHOTO_THUMBNAIL_DATA);
  928.  
  929.             final String displayName = cursor
  930.                     .getString(ContactsQuery.DISPLAY_NAME);
  931.  
  932.             final int startIndex = indexOfSearchQuery(displayName);
  933.  
  934.             if (startIndex == -1)
  935.             {
  936.                 // If the user didn't do a search, or the search string didn't
  937.                 // match a display
  938.                 // name, show the display name without highlighting
  939.                 holder.text1.setText(displayName);
  940.  
  941.                 if (TextUtils.isEmpty(mSearchTerm))
  942.                 {
  943.                     // If the search search is empty, hide the second line of
  944.                     // text
  945.                     holder.text2.setVisibility(View.GONE);
  946.                 } else
  947.                 {
  948.                     // Shows a second line of text that indicates the search
  949.                     // string matched
  950.                     // something other than the display name
  951.                     holder.text2.setVisibility(View.VISIBLE);
  952.                 }
  953.             } else
  954.             {
  955.                 // If the search string matched the display name, applies a
  956.                 // SpannableString to
  957.                 // highlight the search string with the displayed display name
  958.  
  959.                 // Wraps the display name in the SpannableString
  960.                 final SpannableString highlightedName = new SpannableString(
  961.                         displayName);
  962.  
  963.                 // Sets the span to start at the starting point of the match and
  964.                 // end at "length"
  965.                 // characters beyond the starting point
  966.                 highlightedName.setSpan(highlightTextSpan, startIndex,
  967.                         startIndex + mSearchTerm.length(), 0);
  968.  
  969.                 // Binds the SpannableString to the display name View object
  970.                 holder.text1.setText(highlightedName);
  971.  
  972.                 // Since the search string matched the name, this hides the
  973.                 // secondary message
  974.                 holder.text2.setVisibility(View.GONE);
  975.             }
  976.  
  977.             // Processes the QuickContactBadge. A QuickContactBadge first
  978.             // appears as a contact's
  979.             // thumbnail image with styling that indicates it can be touched for
  980.             // additional
  981.             // information. When the user clicks the image, the badge expands
  982.             // into a dialog box
  983.             // containing the contact's details and icons for the built-in apps
  984.             // that can handle
  985.             // each detail type.
  986.  
  987.             // Generates the contact lookup Uri
  988.             final Uri contactUri = Contacts.getLookupUri(
  989.                     cursor.getLong(ContactsQuery.ID),
  990.                     cursor.getString(ContactsQuery.LOOKUP_KEY));
  991.  
  992.             // Binds the contact's lookup Uri to the QuickContactBadge
  993.             holder.icon.assignContactUri(contactUri);
  994.  
  995.             // Loads the thumbnail image pointed to by photoUri into the
  996.             // QuickContactBadge in a
  997.             // background worker thread
  998.             mImageLoader.loadImage(photoUri, holder.icon);
  999.         }
  1000.  
  1001.         /**
  1002.          * Overrides swapCursor to move the new Cursor into the AlphabetIndex as
  1003.          * well as the CursorAdapter.
  1004.          */
  1005.         @Override
  1006.         public Cursor swapCursor(Cursor newCursor)
  1007.         {
  1008.             // Update the AlphabetIndexer with new cursor as well
  1009.             mAlphabetIndexer.setCursor(newCursor);
  1010.             return super.swapCursor(newCursor);
  1011.         }
  1012.  
  1013.         /**
  1014.          * An override of getCount that simplifies accessing the Cursor. If the
  1015.          * Cursor is null, getCount returns zero. As a result, no test for
  1016.          * Cursor == null is needed.
  1017.          */
  1018.         @Override
  1019.         public int getCount()
  1020.         {
  1021.             if (getCursor() == null)
  1022.             {
  1023.                 return 0;
  1024.             }
  1025.             return super.getCount();
  1026.         }
  1027.  
  1028.         /**
  1029.          * Defines the SectionIndexer.getSections() interface.
  1030.          */
  1031.         @Override
  1032.         public Object[] getSections()
  1033.         {
  1034.             return mAlphabetIndexer.getSections();
  1035.         }
  1036.  
  1037.         /**
  1038.          * Defines the SectionIndexer.getPositionForSection() interface.
  1039.          */
  1040.         @Override
  1041.         public int getPositionForSection(int i)
  1042.         {
  1043.             if (getCursor() == null)
  1044.             {
  1045.                 return 0;
  1046.             }
  1047.             return mAlphabetIndexer.getPositionForSection(i);
  1048.         }
  1049.  
  1050.         /**
  1051.          * Defines the SectionIndexer.getSectionForPosition() interface.
  1052.          */
  1053.         @Override
  1054.         public int getSectionForPosition(int i)
  1055.         {
  1056.             if (getCursor() == null)
  1057.             {
  1058.                 return 0;
  1059.             }
  1060.             return mAlphabetIndexer.getSectionForPosition(i);
  1061.         }
  1062.  
  1063.         /**
  1064.          * A class that defines fields for each resource ID in the list item
  1065.          * layout. This allows ContactsAdapter.newView() to store the IDs once,
  1066.          * when it inflates the layout, instead of calling findViewById in each
  1067.          * iteration of bindView.
  1068.          */
  1069.         private class ViewHolder
  1070.         {
  1071.             TextView text1;
  1072.             TextView text2;
  1073.             QuickContactBadge icon;
  1074.         }
  1075.     }
  1076.  
  1077.     /**
  1078.      * This interface must be implemented by any activity that loads this
  1079.      * fragment. When an interaction occurs, such as touching an item from the
  1080.      * ListView, these callbacks will be invoked to communicate the event back
  1081.      * to the activity.
  1082.      */
  1083.     public interface OnContactsInteractionListener
  1084.     {
  1085.         /**
  1086.          * Called when a contact is selected from the ListView.
  1087.          *
  1088.          * @param contactUri
  1089.          *            The contact Uri.
  1090.          */
  1091.         public void onContactSelected(Uri contactUri);
  1092.  
  1093.         /**
  1094.          * Called when the ListView selection is cleared like when a contact
  1095.          * search is taking place or is finishing.
  1096.          */
  1097.         public void onSelectionCleared();
  1098.     }
  1099.  
  1100.     /**
  1101.      * This interface defines constants for the Cursor and CursorLoader, based
  1102.      * on constants defined in the
  1103.      * {@link android.provider.ContactsContract.Contacts} class.
  1104.      */
  1105.     public interface ContactsQuery
  1106.     {
  1107.  
  1108.         // An identifier for the loader
  1109.         final static int QUERY_ID = 1;
  1110.  
  1111.         // A content URI for the Contacts table
  1112.         final static Uri CONTENT_URI = Contacts.CONTENT_URI;
  1113.  
  1114.         // The search/filter query Uri
  1115.         final static Uri FILTER_URI = Contacts.CONTENT_FILTER_URI;
  1116.  
  1117.         // The selection clause for the CursorLoader query. The search criteria
  1118.         // defined here
  1119.         // restrict results to contacts that have a display name and are linked
  1120.         // to visible groups.
  1121.         // Notice that the search on the string provided by the user is
  1122.         // implemented by appending
  1123.         // the search string to CONTENT_FILTER_URI.
  1124.         @SuppressLint("InlinedApi")
  1125.         final static String SELECTION = (Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY
  1126.                 : Contacts.DISPLAY_NAME)
  1127.                 + "<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";
  1128.  
  1129.         // The desired sort order for the returned Cursor. In Android 3.0 and
  1130.         // later, the primary
  1131.         // sort key allows for localization. In earlier versions. use the
  1132.         // display name as the sort
  1133.         // key.
  1134.         @SuppressLint("InlinedApi")
  1135.         final static String SORT_ORDER = Utils.hasHoneycomb() ? Contacts.SORT_KEY_PRIMARY
  1136.                 : Contacts.DISPLAY_NAME;
  1137.  
  1138.         // The projection for the CursorLoader query. This is a list of columns
  1139.         // that the Contacts
  1140.         // Provider should return in the Cursor.
  1141.         @SuppressLint("InlinedApi")
  1142.         final static String[] PROJECTION =
  1143.         {
  1144.  
  1145.                 // The contact's row id
  1146.                 Contacts._ID,
  1147.  
  1148.                 // A pointer to the contact that is guaranteed to be more
  1149.                 // permanent than _ID. Given
  1150.                 // a contact's current _ID value and LOOKUP_KEY, the Contacts
  1151.                 // Provider can generate
  1152.                 // a "permanent" contact URI.
  1153.                 Contacts.LOOKUP_KEY,
  1154.  
  1155.                 // In platform version 3.0 and later, the Contacts table
  1156.                 // contains
  1157.                 // DISPLAY_NAME_PRIMARY, which either contains the contact's
  1158.                 // displayable name or
  1159.                 // some other useful identifier such as an email address. This
  1160.                 // column isn't
  1161.                 // available in earlier versions of Android, so you must use
  1162.                 // Contacts.DISPLAY_NAME
  1163.                 // instead.
  1164.                 Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY
  1165.                         : Contacts.DISPLAY_NAME,
  1166.  
  1167.                 // In Android 3.0 and later, the thumbnail image is pointed to
  1168.                 // by
  1169.                 // PHOTO_THUMBNAIL_URI. In earlier versions, there is no direct
  1170.                 // pointer; instead,
  1171.                 // you generate the pointer from the contact's ID value and
  1172.                 // constants defined in
  1173.                 // android.provider.ContactsContract.Contacts.
  1174.                 Utils.hasHoneycomb() ? Contacts.PHOTO_THUMBNAIL_URI
  1175.                         : Contacts._ID,
  1176.  
  1177.                 // The sort order column for the returned Cursor, used by the
  1178.                 // AlphabetIndexer
  1179.                 SORT_ORDER, };
  1180.  
  1181.         // The query column numbers which map to each value in the projection
  1182.         final static int ID = 0;
  1183.         final static int LOOKUP_KEY = 1;
  1184.         final static int DISPLAY_NAME = 2;
  1185.         final static int PHOTO_THUMBNAIL_DATA = 3;
  1186.         final static int SORT_KEY = 4;
  1187.     }
  1188. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement