| /* |
| * 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.example.android.contactslist.ui; |
| |
| import android.annotation.SuppressLint; |
| import android.annotation.TargetApi; |
| import android.content.ContentResolver; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| 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.provider.ContactsContract.CommonDataKinds.StructuredPostal; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Contacts.Photo; |
| import android.provider.ContactsContract.Data; |
| import android.support.v4.app.Fragment; |
| import android.support.v4.app.LoaderManager; |
| import android.support.v4.content.CursorLoader; |
| import android.support.v4.content.Loader; |
| import android.util.DisplayMetrics; |
| import android.util.Log; |
| 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.ImageButton; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import com.example.android.contactslist.BuildConfig; |
| import com.example.android.contactslist.R; |
| import com.example.android.contactslist.util.ImageLoader; |
| import com.example.android.contactslist.util.Utils; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| |
| /** |
| * This fragment displays details of a specific contact from the contacts provider. It shows the |
| * contact's display photo, name and all its mailing addresses. You can also modify this fragment |
| * to show other information, such as phone numbers, email addresses and so forth. |
| * |
| * This fragment appears full-screen in an activity on devices with small screen sizes, and as |
| * part of a two-pane layout on devices with larger screens, alongside the |
| * {@link ContactsListFragment}. |
| * |
| * To create an instance of this fragment, use the factory method |
| * {@link ContactDetailFragment#newInstance(android.net.Uri)}, passing as an argument the contact |
| * Uri for the contact you want to display. |
| */ |
| public class ContactDetailFragment extends Fragment implements |
| LoaderManager.LoaderCallbacks<Cursor> { |
| |
| public static final String EXTRA_CONTACT_URI = |
| "com.example.android.contactslist.ui.EXTRA_CONTACT_URI"; |
| |
| // Defines a tag for identifying log entries |
| private static final String TAG = "ContactDetailFragment"; |
| |
| // The geo Uri scheme prefix, used with Intent.ACTION_VIEW to form a geographical address |
| // intent that will trigger available apps to handle viewing a location (such as Maps) |
| private static final String GEO_URI_SCHEME_PREFIX = "geo:0,0?q="; |
| |
| // Whether or not this fragment is showing in a two pane layout |
| private boolean mIsTwoPaneLayout; |
| |
| private Uri mContactUri; // Stores the contact Uri for this fragment instance |
| private ImageLoader mImageLoader; // Handles loading the contact image in a background thread |
| |
| // Used to store references to key views, layouts and menu items as these need to be updated |
| // in multiple methods throughout this class. |
| private ImageView mImageView; |
| private LinearLayout mDetailsLayout; |
| private TextView mEmptyView; |
| private TextView mContactName; |
| private MenuItem mEditContactMenuItem; |
| |
| /** |
| * Factory method to generate a new instance of the fragment given a contact Uri. A factory |
| * method is preferable to simply using the constructor as it handles creating the bundle and |
| * setting the bundle as an argument. |
| * |
| * @param contactUri The contact Uri to load |
| * @return A new instance of {@link ContactDetailFragment} |
| */ |
| public static ContactDetailFragment newInstance(Uri contactUri) { |
| // Create new instance of this fragment |
| final ContactDetailFragment fragment = new ContactDetailFragment(); |
| |
| // Create and populate the args bundle |
| final Bundle args = new Bundle(); |
| args.putParcelable(EXTRA_CONTACT_URI, contactUri); |
| |
| // Assign the args bundle to the new fragment |
| fragment.setArguments(args); |
| |
| // Return fragment |
| return fragment; |
| } |
| |
| /** |
| * Fragments require an empty constructor. |
| */ |
| public ContactDetailFragment() {} |
| |
| /** |
| * Sets the contact that this Fragment displays, or clears the display if the contact argument |
| * is null. This will re-initialize all the views and start the queries to the system contacts |
| * provider to populate the contact information. |
| * |
| * @param contactLookupUri The contact lookup Uri to load and display in this fragment. Passing |
| * null is valid and the fragment will display a message that no |
| * contact is currently selected instead. |
| */ |
| public void setContact(Uri contactLookupUri) { |
| |
| // In version 3.0 and later, stores the provided contact lookup Uri in a class field. This |
| // Uri is then used at various points in this class to map to the provided contact. |
| if (Utils.hasHoneycomb()) { |
| mContactUri = contactLookupUri; |
| } else { |
| // For versions earlier than Android 3.0, stores a contact Uri that's constructed from |
| // contactLookupUri. Later on, the resulting Uri is combined with |
| // Contacts.Data.CONTENT_DIRECTORY to map to the provided contact. It's done |
| // differently for these earlier versions because Contacts.Data.CONTENT_DIRECTORY works |
| // differently for Android versions before 3.0. |
| mContactUri = Contacts.lookupContact(getActivity().getContentResolver(), |
| contactLookupUri); |
| } |
| |
| // If the Uri contains data, load the contact's image and load contact details. |
| if (contactLookupUri != null) { |
| // Asynchronously loads the contact image |
| mImageLoader.loadImage(mContactUri, mImageView); |
| |
| // Shows the contact photo ImageView and hides the empty view |
| mImageView.setVisibility(View.VISIBLE); |
| mEmptyView.setVisibility(View.GONE); |
| |
| // Shows the edit contact action/menu item |
| if (mEditContactMenuItem != null) { |
| mEditContactMenuItem.setVisible(true); |
| } |
| |
| // Starts two queries to to retrieve contact information from the Contacts Provider. |
| // restartLoader() is used instead of initLoader() as this method may be called |
| // multiple times. |
| getLoaderManager().restartLoader(ContactDetailQuery.QUERY_ID, null, this); |
| getLoaderManager().restartLoader(ContactAddressQuery.QUERY_ID, null, this); |
| } else { |
| // If contactLookupUri is null, then the method was called when no contact was selected |
| // in the contacts list. This should only happen in a two-pane layout when the user |
| // hasn't yet selected a contact. Don't display an image for the contact, and don't |
| // account for the view's space in the layout. Turn on the TextView that appears when |
| // the layout is empty, and set the contact name to the empty string. Turn off any menu |
| // items that are visible. |
| mImageView.setVisibility(View.GONE); |
| mEmptyView.setVisibility(View.VISIBLE); |
| mDetailsLayout.removeAllViews(); |
| if (mContactName != null) { |
| mContactName.setText(""); |
| } |
| if (mEditContactMenuItem != null) { |
| mEditContactMenuItem.setVisible(false); |
| } |
| } |
| } |
| |
| /** |
| * When the Fragment is first created, this callback is invoked. It initializes some key |
| * class fields. |
| */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| // Check if this fragment is part of a two pane set up or a single pane |
| mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes); |
| |
| // Let this fragment contribute menu items |
| setHasOptionsMenu(true); |
| |
| /* |
| * The ImageLoader takes care of loading and resizing images asynchronously into the |
| * ImageView. More thorough sample code demonstrating background image loading as well as |
| * details on how it works can be found in the following Android Training class: |
| * http://developer.android.com/training/displaying-bitmaps/ |
| */ |
| mImageLoader = new ImageLoader(getActivity(), getLargestScreenDimension()) { |
| @Override |
| protected Bitmap processBitmap(Object data) { |
| // This gets called in a background thread and passed the data from |
| // ImageLoader.loadImage(). |
| return loadContactPhoto((Uri) data, getImageSize()); |
| |
| } |
| }; |
| |
| // Set a placeholder loading image for the image loader |
| mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_180_holo_light); |
| |
| // Tell the image loader to set the image directly when it's finished loading |
| // rather than fading in |
| mImageLoader.setImageFadeIn(false); |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| |
| // Inflates the main layout to be used by this fragment |
| final View detailView = |
| inflater.inflate(R.layout.contact_detail_fragment, container, false); |
| |
| // Gets handles to view objects in the layout |
| mImageView = (ImageView) detailView.findViewById(R.id.contact_image); |
| mDetailsLayout = (LinearLayout) detailView.findViewById(R.id.contact_details_layout); |
| mEmptyView = (TextView) detailView.findViewById(android.R.id.empty); |
| |
| if (mIsTwoPaneLayout) { |
| // If this is a two pane view, the following code changes the visibility of the contact |
| // name in details. For a one-pane view, the contact name is displayed as a title. |
| mContactName = (TextView) detailView.findViewById(R.id.contact_name); |
| mContactName.setVisibility(View.VISIBLE); |
| } |
| |
| return detailView; |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| // If not being created from a previous state |
| if (savedInstanceState == null) { |
| // Sets the argument extra as the currently displayed contact |
| setContact(getArguments() != null ? |
| (Uri) getArguments().getParcelable(EXTRA_CONTACT_URI) : null); |
| } else { |
| // If being recreated from a saved state, sets the contact from the incoming |
| // savedInstanceState Bundle |
| setContact((Uri) savedInstanceState.getParcelable(EXTRA_CONTACT_URI)); |
| } |
| } |
| |
| /** |
| * When the Fragment is being saved in order to change activity state, save the |
| * currently-selected contact. |
| */ |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| // Saves the contact Uri |
| outState.putParcelable(EXTRA_CONTACT_URI, mContactUri); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| // When "edit" menu option selected |
| case R.id.menu_edit_contact: |
| // Standard system edit contact intent |
| Intent intent = new Intent(Intent.ACTION_EDIT, mContactUri); |
| |
| // Because of an issue in Android 4.0 (API level 14), clicking Done or Back in the |
| // People app doesn't return the user to your app; instead, it displays the People |
| // app's contact list. A workaround, introduced in Android 4.0.3 (API level 15) is |
| // to set a special flag in the extended data for the Intent you send to the People |
| // app. The issue is does not appear in versions prior to Android 4.0. You can use |
| // the flag with any version of the People app; if the workaround isn't needed, |
| // the flag is ignored. |
| intent.putExtra("finishActivityOnSaveCompleted", true); |
| |
| // Start the edit activity |
| startActivity(intent); |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override |
| public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
| super.onCreateOptionsMenu(menu, inflater); |
| |
| // Inflates the options menu for this fragment |
| inflater.inflate(R.menu.contact_detail_menu, menu); |
| |
| // Gets a handle to the "find" menu item |
| mEditContactMenuItem = menu.findItem(R.id.menu_edit_contact); |
| |
| // If contactUri is null the edit menu item should be hidden, otherwise |
| // it is visible. |
| mEditContactMenuItem.setVisible(mContactUri != null); |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| switch (id) { |
| // Two main queries to load the required information |
| case ContactDetailQuery.QUERY_ID: |
| // This query loads main contact details, see |
| // ContactDetailQuery for more information. |
| return new CursorLoader(getActivity(), mContactUri, |
| ContactDetailQuery.PROJECTION, |
| null, null, null); |
| case ContactAddressQuery.QUERY_ID: |
| // This query loads contact address details, see |
| // ContactAddressQuery for more information. |
| final Uri uri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY); |
| return new CursorLoader(getActivity(), uri, |
| ContactAddressQuery.PROJECTION, |
| ContactAddressQuery.SELECTION, |
| null, null); |
| } |
| return null; |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
| |
| // If this fragment was cleared while the query was running |
| // eg. from from a call like setContact(uri) then don't do |
| // anything. |
| if (mContactUri == null) { |
| return; |
| } |
| |
| switch (loader.getId()) { |
| case ContactDetailQuery.QUERY_ID: |
| // Moves to the first row in the Cursor |
| if (data.moveToFirst()) { |
| // For the contact details query, fetches the contact display name. |
| // ContactDetailQuery.DISPLAY_NAME maps to the appropriate display |
| // name field based on OS version. |
| final String contactName = data.getString(ContactDetailQuery.DISPLAY_NAME); |
| if (mIsTwoPaneLayout && mContactName != null) { |
| // In the two pane layout, there is a dedicated TextView |
| // that holds the contact name. |
| mContactName.setText(contactName); |
| } else { |
| // In the single pane layout, sets the activity title |
| // to the contact name. On HC+ this will be set as |
| // the ActionBar title text. |
| getActivity().setTitle(contactName); |
| } |
| } |
| break; |
| case ContactAddressQuery.QUERY_ID: |
| // This query loads the contact address details. More than |
| // one contact address is possible, so move each one to a |
| // LinearLayout in a Scrollview so multiple addresses can |
| // be scrolled by the user. |
| |
| // Each LinearLayout has the same LayoutParams so this can |
| // be created once and used for each address. |
| final LinearLayout.LayoutParams layoutParams = |
| new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT); |
| |
| // Clears out the details layout first in case the details |
| // layout has addresses from a previous data load still |
| // added as children. |
| mDetailsLayout.removeAllViews(); |
| |
| // Loops through all the rows in the Cursor |
| if (data.moveToFirst()) { |
| do { |
| // Builds the address layout |
| final LinearLayout layout = buildAddressLayout( |
| data.getInt(ContactAddressQuery.TYPE), |
| data.getString(ContactAddressQuery.LABEL), |
| data.getString(ContactAddressQuery.ADDRESS)); |
| // Adds the new address layout to the details layout |
| mDetailsLayout.addView(layout, layoutParams); |
| } while (data.moveToNext()); |
| } else { |
| // If nothing found, adds an empty address layout |
| mDetailsLayout.addView(buildEmptyAddressLayout(), layoutParams); |
| } |
| break; |
| } |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> loader) { |
| // Nothing to do here. The Cursor does not need to be released as it was never directly |
| // bound to anything (like an adapter). |
| } |
| |
| /** |
| * Builds an empty address layout that just shows that no addresses |
| * were found for this contact. |
| * |
| * @return A LinearLayout to add to the contact details layout |
| */ |
| private LinearLayout buildEmptyAddressLayout() { |
| return buildAddressLayout(0, null, null); |
| } |
| |
| /** |
| * Builds an address LinearLayout based on address information from the Contacts Provider. |
| * Each address for the contact gets its own LinearLayout object; for example, if the contact |
| * has three postal addresses, then 3 LinearLayouts are generated. |
| * |
| * @param addressType From |
| * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#TYPE} |
| * @param addressTypeLabel From |
| * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#LABEL} |
| * @param address From |
| * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#FORMATTED_ADDRESS} |
| * @return A LinearLayout to add to the contact details layout, |
| * populated with the provided address details. |
| */ |
| private LinearLayout buildAddressLayout(int addressType, String addressTypeLabel, |
| final String address) { |
| |
| // Inflates the address layout |
| final LinearLayout addressLayout = |
| (LinearLayout) LayoutInflater.from(getActivity()).inflate( |
| R.layout.contact_detail_item, mDetailsLayout, false); |
| |
| // Gets handles to the view objects in the layout |
| final TextView headerTextView = |
| (TextView) addressLayout.findViewById(R.id.contact_detail_header); |
| final TextView addressTextView = |
| (TextView) addressLayout.findViewById(R.id.contact_detail_item); |
| final ImageButton viewAddressButton = |
| (ImageButton) addressLayout.findViewById(R.id.button_view_address); |
| |
| // If there's no addresses for the contact, shows the empty view and message, and hides the |
| // header and button. |
| if (addressTypeLabel == null && addressType == 0) { |
| headerTextView.setVisibility(View.GONE); |
| viewAddressButton.setVisibility(View.GONE); |
| addressTextView.setText(R.string.no_address); |
| } else { |
| // Gets postal address label type |
| CharSequence label = |
| StructuredPostal.getTypeLabel(getResources(), addressType, addressTypeLabel); |
| |
| // Sets TextView objects in the layout |
| headerTextView.setText(label); |
| addressTextView.setText(address); |
| |
| // Defines an onClickListener object for the address button |
| viewAddressButton.setOnClickListener(new View.OnClickListener() { |
| // Defines what to do when users click the address button |
| @Override |
| public void onClick(View view) { |
| |
| final Intent viewIntent = |
| new Intent(Intent.ACTION_VIEW, constructGeoUri(address)); |
| |
| // A PackageManager instance is needed to verify that there's a default app |
| // that handles ACTION_VIEW and a geo Uri. |
| final PackageManager packageManager = getActivity().getPackageManager(); |
| |
| // Checks for an activity that can handle this intent. Preferred in this |
| // case over Intent.createChooser() as it will still let the user choose |
| // a default (or use a previously set default) for geo Uris. |
| if (packageManager.resolveActivity( |
| viewIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) { |
| startActivity(viewIntent); |
| } else { |
| // If no default is found, displays a message that no activity can handle |
| // the view button. |
| Toast.makeText(getActivity(), |
| R.string.no_intent_found, Toast.LENGTH_SHORT).show(); |
| } |
| } |
| }); |
| |
| } |
| return addressLayout; |
| } |
| |
| /** |
| * Constructs a geo scheme Uri from a postal address. |
| * |
| * @param postalAddress A postal address. |
| * @return the geo:// Uri for the postal address. |
| */ |
| private Uri constructGeoUri(String postalAddress) { |
| // Concatenates the geo:// prefix to the postal address. The postal address must be |
| // converted to Uri format and encoded for special characters. |
| return Uri.parse(GEO_URI_SCHEME_PREFIX + Uri.encode(postalAddress)); |
| } |
| |
| /** |
| * Fetches the width or height of the screen in pixels, whichever is larger. This is used to |
| * set a maximum size limit on the contact photo that is retrieved from the Contacts Provider. |
| * This limit prevents the app from trying to decode and load an image that is much larger than |
| * the available screen area. |
| * |
| * @return The largest screen dimension in pixels. |
| */ |
| private int getLargestScreenDimension() { |
| // Gets a DisplayMetrics object, which is used to retrieve the display's pixel height and |
| // width |
| final DisplayMetrics displayMetrics = new DisplayMetrics(); |
| |
| // Retrieves a displayMetrics object for the device's default display |
| getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); |
| final int height = displayMetrics.heightPixels; |
| final int width = displayMetrics.widthPixels; |
| |
| // Returns the larger of the two values |
| return height > width ? height : width; |
| } |
| |
| /** |
| * Decodes and returns the contact's thumbnail image. |
| * @param contactUri The Uri of the contact containing the image. |
| * @param imageSize The desired target width and height of the output image in pixels. |
| * @return If a thumbnail image exists for the contact, a Bitmap image, otherwise null. |
| */ |
| @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) |
| private Bitmap loadContactPhoto(Uri contactUri, 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 a ContentResolver for retrieving the Uri of the image |
| final ContentResolver contentResolver = getActivity().getContentResolver(); |
| |
| // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the |
| // ContentResolver can return an AssetFileDescriptor for the file. |
| AssetFileDescriptor afd = null; |
| |
| if (Utils.hasICS()) { |
| // On platforms running Android 4.0 (API version 14) and later, a high resolution image |
| // is available from Photo.DISPLAY_PHOTO. |
| try { |
| // Constructs the content Uri for the image |
| Uri displayImageUri = Uri.withAppendedPath(contactUri, Photo.DISPLAY_PHOTO); |
| |
| // Retrieves an AssetFileDescriptor from the Contacts Provider, using the |
| // constructed Uri |
| afd = contentResolver.openAssetFileDescriptor(displayImageUri, "r"); |
| // If the file exists |
| if (afd != null) { |
| // Reads and decodes the file to a Bitmap and scales it to the desired size |
| return ImageLoader.decodeSampledBitmapFromDescriptor( |
| afd.getFileDescriptor(), imageSize, imageSize); |
| } |
| } catch (FileNotFoundException e) { |
| // Catches file not found exceptions |
| if (BuildConfig.DEBUG) { |
| // Log debug message, this is not an error message as this exception is thrown |
| // when a contact is legitimately missing a contact photo (which will be quite |
| // frequently in a long contacts list). |
| Log.d(TAG, "Contact photo not found for contact " + contactUri.toString() |
| + ": " + e.toString()); |
| } |
| } finally { |
| // Once the decode is complete, this closes the file. You must do this each time |
| // you access an AssetFileDescriptor; otherwise, every image load you do will open |
| // a new descriptor. |
| 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 platform version is less than Android 4.0 (API Level 14), use the only available |
| // image URI, which points to a normal-sized image. |
| try { |
| // Constructs the image Uri from the contact Uri and the directory twig from the |
| // Contacts.Photo table |
| Uri imageUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY); |
| |
| // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed |
| // Uri |
| afd = getActivity().getContentResolver().openAssetFileDescriptor(imageUri, "r"); |
| |
| // If the file exists |
| if (afd != null) { |
| // Reads the image from the file, decodes it, and scales it to the available screen |
| // area |
| return ImageLoader.decodeSampledBitmapFromDescriptor( |
| afd.getFileDescriptor(), imageSize, imageSize); |
| } |
| } catch (FileNotFoundException e) { |
| // Catches file not found exceptions |
| if (BuildConfig.DEBUG) { |
| // Log debug message, this is not an error message as this exception is thrown |
| // when a contact is legitimately missing a contact photo (which will be quite |
| // frequently in a long contacts list). |
| Log.d(TAG, "Contact photo not found for contact " + contactUri.toString() |
| + ": " + e.toString()); |
| } |
| } finally { |
| // Once the decode is complete, this closes the file. You must do this each time you |
| // access an AssetFileDescriptor; otherwise, every image load you do will open a new |
| // descriptor. |
| if (afd != null) { |
| try { |
| afd.close(); |
| } catch (IOException e) { |
| // Closing a file descriptor might cause an IOException if the file is |
| // already closed. Ignore this. |
| } |
| } |
| } |
| |
| // If none of the case selectors match, returns null. |
| return null; |
| } |
| |
| /** |
| * This interface defines constants used by contact retrieval queries. |
| */ |
| public interface ContactDetailQuery { |
| // A unique query ID to distinguish queries being run by the |
| // LoaderManager. |
| final static int QUERY_ID = 1; |
| |
| // The query projection (columns to fetch from the provider) |
| @SuppressLint("InlinedApi") |
| final static String[] PROJECTION = { |
| Contacts._ID, |
| Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME, |
| }; |
| |
| // The query column numbers which map to each value in the projection |
| final static int ID = 0; |
| final static int DISPLAY_NAME = 1; |
| } |
| |
| /** |
| * This interface defines constants used by address retrieval queries. |
| */ |
| public interface ContactAddressQuery { |
| // A unique query ID to distinguish queries being run by the |
| // LoaderManager. |
| final static int QUERY_ID = 2; |
| |
| // The query projection (columns to fetch from the provider) |
| final static String[] PROJECTION = { |
| StructuredPostal._ID, |
| StructuredPostal.FORMATTED_ADDRESS, |
| StructuredPostal.TYPE, |
| StructuredPostal.LABEL, |
| }; |
| |
| // The query selection criteria. In this case matching against the |
| // StructuredPostal content mime type. |
| final static String SELECTION = |
| Data.MIMETYPE + "='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"; |
| |
| // The query column numbers which map to each value in the projection |
| final static int ID = 0; |
| final static int ADDRESS = 1; |
| final static int TYPE = 2; |
| final static int LABEL = 3; |
| } |
| } |