@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { // For platforms earlier than Android 3.0, triggers the search activity case R.id.menu_search: if (!Utils.hasHoneycomb()) { getActivity().onSearchRequested(); } break; } return super.onOptionsItemSelected(item); }
/** * 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 static final int QUERY_ID = 1; // A content URI for the Contacts table static final Uri CONTENT_URI = Contacts.CONTENT_URI; // The search/filter query Uri static final 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") static final 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") static final 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") static final 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, }; // The query column numbers which map to each value in the projection static final int ID = 0; static final int LOOKUP_KEY = 1; static final int DISPLAY_NAME = 2; static final int PHOTO_THUMBNAIL_DATA = 3; static final int SORT_KEY = 4; }
/** * 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 method uses APIs from newer OS versions 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.contacts_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) searchItem.getActionView(); // 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; // 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; 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); } } }