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.content.ContentResolver; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.content.res.AssetFileDescriptor; 25 import android.database.Cursor; 26 import android.graphics.Bitmap; 27 import android.net.Uri; 28 import android.os.Build; 29 import android.os.Bundle; 30 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 31 import android.provider.ContactsContract.Contacts; 32 import android.provider.ContactsContract.Contacts.Photo; 33 import android.provider.ContactsContract.Data; 34 import android.support.v4.app.Fragment; 35 import android.support.v4.app.LoaderManager; 36 import android.support.v4.content.CursorLoader; 37 import android.support.v4.content.Loader; 38 import android.util.DisplayMetrics; 39 import android.util.Log; 40 import android.view.LayoutInflater; 41 import android.view.Menu; 42 import android.view.MenuInflater; 43 import android.view.MenuItem; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.widget.ImageButton; 47 import android.widget.ImageView; 48 import android.widget.LinearLayout; 49 import android.widget.TextView; 50 import android.widget.Toast; 51 52 import com.example.android.contactslist.BuildConfig; 53 import com.example.android.contactslist.R; 54 import com.example.android.contactslist.util.ImageLoader; 55 import com.example.android.contactslist.util.Utils; 56 57 import java.io.FileNotFoundException; 58 import java.io.IOException; 59 60 /** 61 * This fragment displays details of a specific contact from the contacts provider. It shows the 62 * contact's display photo, name and all its mailing addresses. You can also modify this fragment 63 * to show other information, such as phone numbers, email addresses and so forth. 64 * 65 * This fragment appears full-screen in an activity on devices with small screen sizes, and as 66 * part of a two-pane layout on devices with larger screens, alongside the 67 * {@link ContactsListFragment}. 68 * 69 * To create an instance of this fragment, use the factory method 70 * {@link ContactDetailFragment#newInstance(android.net.Uri)}, passing as an argument the contact 71 * Uri for the contact you want to display. 72 */ 73 public class ContactDetailFragment extends Fragment implements 74 LoaderManager.LoaderCallbacks<Cursor> { 75 76 public static final String EXTRA_CONTACT_URI = 77 "com.example.android.contactslist.ui.EXTRA_CONTACT_URI"; 78 79 // Defines a tag for identifying log entries 80 private static final String TAG = "ContactDetailFragment"; 81 82 // The geo Uri scheme prefix, used with Intent.ACTION_VIEW to form a geographical address 83 // intent that will trigger available apps to handle viewing a location (such as Maps) 84 private static final String GEO_URI_SCHEME_PREFIX = "geo:0,0?q="; 85 86 // Whether or not this fragment is showing in a two pane layout 87 private boolean mIsTwoPaneLayout; 88 89 private Uri mContactUri; // Stores the contact Uri for this fragment instance 90 private ImageLoader mImageLoader; // Handles loading the contact image in a background thread 91 92 // Used to store references to key views, layouts and menu items as these need to be updated 93 // in multiple methods throughout this class. 94 private ImageView mImageView; 95 private LinearLayout mDetailsLayout; 96 private TextView mEmptyView; 97 private TextView mContactName; 98 private MenuItem mEditContactMenuItem; 99 100 /** 101 * Factory method to generate a new instance of the fragment given a contact Uri. A factory 102 * method is preferable to simply using the constructor as it handles creating the bundle and 103 * setting the bundle as an argument. 104 * 105 * @param contactUri The contact Uri to load 106 * @return A new instance of {@link ContactDetailFragment} 107 */ newInstance(Uri contactUri)108 public static ContactDetailFragment newInstance(Uri contactUri) { 109 // Create new instance of this fragment 110 final ContactDetailFragment fragment = new ContactDetailFragment(); 111 112 // Create and populate the args bundle 113 final Bundle args = new Bundle(); 114 args.putParcelable(EXTRA_CONTACT_URI, contactUri); 115 116 // Assign the args bundle to the new fragment 117 fragment.setArguments(args); 118 119 // Return fragment 120 return fragment; 121 } 122 123 /** 124 * Fragments require an empty constructor. 125 */ ContactDetailFragment()126 public ContactDetailFragment() {} 127 128 /** 129 * Sets the contact that this Fragment displays, or clears the display if the contact argument 130 * is null. This will re-initialize all the views and start the queries to the system contacts 131 * provider to populate the contact information. 132 * 133 * @param contactLookupUri The contact lookup Uri to load and display in this fragment. Passing 134 * null is valid and the fragment will display a message that no 135 * contact is currently selected instead. 136 */ setContact(Uri contactLookupUri)137 public void setContact(Uri contactLookupUri) { 138 139 // In version 3.0 and later, stores the provided contact lookup Uri in a class field. This 140 // Uri is then used at various points in this class to map to the provided contact. 141 if (Utils.hasHoneycomb()) { 142 mContactUri = contactLookupUri; 143 } else { 144 // For versions earlier than Android 3.0, stores a contact Uri that's constructed from 145 // contactLookupUri. Later on, the resulting Uri is combined with 146 // Contacts.Data.CONTENT_DIRECTORY to map to the provided contact. It's done 147 // differently for these earlier versions because Contacts.Data.CONTENT_DIRECTORY works 148 // differently for Android versions before 3.0. 149 mContactUri = Contacts.lookupContact(getActivity().getContentResolver(), 150 contactLookupUri); 151 } 152 153 // If the Uri contains data, load the contact's image and load contact details. 154 if (contactLookupUri != null) { 155 // Asynchronously loads the contact image 156 mImageLoader.loadImage(mContactUri, mImageView); 157 158 // Shows the contact photo ImageView and hides the empty view 159 mImageView.setVisibility(View.VISIBLE); 160 mEmptyView.setVisibility(View.GONE); 161 162 // Shows the edit contact action/menu item 163 if (mEditContactMenuItem != null) { 164 mEditContactMenuItem.setVisible(true); 165 } 166 167 // Starts two queries to to retrieve contact information from the Contacts Provider. 168 // restartLoader() is used instead of initLoader() as this method may be called 169 // multiple times. 170 getLoaderManager().restartLoader(ContactDetailQuery.QUERY_ID, null, this); 171 getLoaderManager().restartLoader(ContactAddressQuery.QUERY_ID, null, this); 172 } else { 173 // If contactLookupUri is null, then the method was called when no contact was selected 174 // in the contacts list. This should only happen in a two-pane layout when the user 175 // hasn't yet selected a contact. Don't display an image for the contact, and don't 176 // account for the view's space in the layout. Turn on the TextView that appears when 177 // the layout is empty, and set the contact name to the empty string. Turn off any menu 178 // items that are visible. 179 mImageView.setVisibility(View.GONE); 180 mEmptyView.setVisibility(View.VISIBLE); 181 mDetailsLayout.removeAllViews(); 182 if (mContactName != null) { 183 mContactName.setText(""); 184 } 185 if (mEditContactMenuItem != null) { 186 mEditContactMenuItem.setVisible(false); 187 } 188 } 189 } 190 191 /** 192 * When the Fragment is first created, this callback is invoked. It initializes some key 193 * class fields. 194 */ 195 @Override onCreate(Bundle savedInstanceState)196 public void onCreate(Bundle savedInstanceState) { 197 super.onCreate(savedInstanceState); 198 199 // Check if this fragment is part of a two pane set up or a single pane 200 mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes); 201 202 // Let this fragment contribute menu items 203 setHasOptionsMenu(true); 204 205 /* 206 * The ImageLoader takes care of loading and resizing images asynchronously into the 207 * ImageView. More thorough sample code demonstrating background image loading as well as 208 * details on how it works can be found in the following Android Training class: 209 * http://developer.android.com/training/displaying-bitmaps/ 210 */ 211 mImageLoader = new ImageLoader(getActivity(), getLargestScreenDimension()) { 212 @Override 213 protected Bitmap processBitmap(Object data) { 214 // This gets called in a background thread and passed the data from 215 // ImageLoader.loadImage(). 216 return loadContactPhoto((Uri) data, getImageSize()); 217 218 } 219 }; 220 221 // Set a placeholder loading image for the image loader 222 mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_180_holo_light); 223 224 // Tell the image loader to set the image directly when it's finished loading 225 // rather than fading in 226 mImageLoader.setImageFadeIn(false); 227 } 228 229 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)230 public View onCreateView(LayoutInflater inflater, ViewGroup container, 231 Bundle savedInstanceState) { 232 233 // Inflates the main layout to be used by this fragment 234 final View detailView = 235 inflater.inflate(R.layout.contact_detail_fragment, container, false); 236 237 // Gets handles to view objects in the layout 238 mImageView = (ImageView) detailView.findViewById(R.id.contact_image); 239 mDetailsLayout = (LinearLayout) detailView.findViewById(R.id.contact_details_layout); 240 mEmptyView = (TextView) detailView.findViewById(android.R.id.empty); 241 242 if (mIsTwoPaneLayout) { 243 // If this is a two pane view, the following code changes the visibility of the contact 244 // name in details. For a one-pane view, the contact name is displayed as a title. 245 mContactName = (TextView) detailView.findViewById(R.id.contact_name); 246 mContactName.setVisibility(View.VISIBLE); 247 } 248 249 return detailView; 250 } 251 252 @Override onActivityCreated(Bundle savedInstanceState)253 public void onActivityCreated(Bundle savedInstanceState) { 254 super.onActivityCreated(savedInstanceState); 255 // If not being created from a previous state 256 if (savedInstanceState == null) { 257 // Sets the argument extra as the currently displayed contact 258 setContact(getArguments() != null ? 259 (Uri) getArguments().getParcelable(EXTRA_CONTACT_URI) : null); 260 } else { 261 // If being recreated from a saved state, sets the contact from the incoming 262 // savedInstanceState Bundle 263 setContact((Uri) savedInstanceState.getParcelable(EXTRA_CONTACT_URI)); 264 } 265 } 266 267 /** 268 * When the Fragment is being saved in order to change activity state, save the 269 * currently-selected contact. 270 */ 271 @Override onSaveInstanceState(Bundle outState)272 public void onSaveInstanceState(Bundle outState) { 273 super.onSaveInstanceState(outState); 274 // Saves the contact Uri 275 outState.putParcelable(EXTRA_CONTACT_URI, mContactUri); 276 } 277 278 @Override onOptionsItemSelected(MenuItem item)279 public boolean onOptionsItemSelected(MenuItem item) { 280 switch (item.getItemId()) { 281 // When "edit" menu option selected 282 case R.id.menu_edit_contact: 283 // Standard system edit contact intent 284 Intent intent = new Intent(Intent.ACTION_EDIT, mContactUri); 285 286 // Because of an issue in Android 4.0 (API level 14), clicking Done or Back in the 287 // People app doesn't return the user to your app; instead, it displays the People 288 // app's contact list. A workaround, introduced in Android 4.0.3 (API level 15) is 289 // to set a special flag in the extended data for the Intent you send to the People 290 // app. The issue is does not appear in versions prior to Android 4.0. You can use 291 // the flag with any version of the People app; if the workaround isn't needed, 292 // the flag is ignored. 293 intent.putExtra("finishActivityOnSaveCompleted", true); 294 295 // Start the edit activity 296 startActivity(intent); 297 return true; 298 } 299 return super.onOptionsItemSelected(item); 300 } 301 302 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)303 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 304 super.onCreateOptionsMenu(menu, inflater); 305 306 // Inflates the options menu for this fragment 307 inflater.inflate(R.menu.contact_detail_menu, menu); 308 309 // Gets a handle to the "find" menu item 310 mEditContactMenuItem = menu.findItem(R.id.menu_edit_contact); 311 312 // If contactUri is null the edit menu item should be hidden, otherwise 313 // it is visible. 314 mEditContactMenuItem.setVisible(mContactUri != null); 315 } 316 317 @Override onCreateLoader(int id, Bundle args)318 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 319 switch (id) { 320 // Two main queries to load the required information 321 case ContactDetailQuery.QUERY_ID: 322 // This query loads main contact details, see 323 // ContactDetailQuery for more information. 324 return new CursorLoader(getActivity(), mContactUri, 325 ContactDetailQuery.PROJECTION, 326 null, null, null); 327 case ContactAddressQuery.QUERY_ID: 328 // This query loads contact address details, see 329 // ContactAddressQuery for more information. 330 final Uri uri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY); 331 return new CursorLoader(getActivity(), uri, 332 ContactAddressQuery.PROJECTION, 333 ContactAddressQuery.SELECTION, 334 null, null); 335 } 336 return null; 337 } 338 339 @Override onLoadFinished(Loader<Cursor> loader, Cursor data)340 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 341 342 // If this fragment was cleared while the query was running 343 // eg. from from a call like setContact(uri) then don't do 344 // anything. 345 if (mContactUri == null) { 346 return; 347 } 348 349 switch (loader.getId()) { 350 case ContactDetailQuery.QUERY_ID: 351 // Moves to the first row in the Cursor 352 if (data.moveToFirst()) { 353 // For the contact details query, fetches the contact display name. 354 // ContactDetailQuery.DISPLAY_NAME maps to the appropriate display 355 // name field based on OS version. 356 final String contactName = data.getString(ContactDetailQuery.DISPLAY_NAME); 357 if (mIsTwoPaneLayout && mContactName != null) { 358 // In the two pane layout, there is a dedicated TextView 359 // that holds the contact name. 360 mContactName.setText(contactName); 361 } else { 362 // In the single pane layout, sets the activity title 363 // to the contact name. On HC+ this will be set as 364 // the ActionBar title text. 365 getActivity().setTitle(contactName); 366 } 367 } 368 break; 369 case ContactAddressQuery.QUERY_ID: 370 // This query loads the contact address details. More than 371 // one contact address is possible, so move each one to a 372 // LinearLayout in a Scrollview so multiple addresses can 373 // be scrolled by the user. 374 375 // Each LinearLayout has the same LayoutParams so this can 376 // be created once and used for each address. 377 final LinearLayout.LayoutParams layoutParams = 378 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 379 ViewGroup.LayoutParams.WRAP_CONTENT); 380 381 // Clears out the details layout first in case the details 382 // layout has addresses from a previous data load still 383 // added as children. 384 mDetailsLayout.removeAllViews(); 385 386 // Loops through all the rows in the Cursor 387 if (data.moveToFirst()) { 388 do { 389 // Builds the address layout 390 final LinearLayout layout = buildAddressLayout( 391 data.getInt(ContactAddressQuery.TYPE), 392 data.getString(ContactAddressQuery.LABEL), 393 data.getString(ContactAddressQuery.ADDRESS)); 394 // Adds the new address layout to the details layout 395 mDetailsLayout.addView(layout, layoutParams); 396 } while (data.moveToNext()); 397 } else { 398 // If nothing found, adds an empty address layout 399 mDetailsLayout.addView(buildEmptyAddressLayout(), layoutParams); 400 } 401 break; 402 } 403 } 404 405 @Override onLoaderReset(Loader<Cursor> loader)406 public void onLoaderReset(Loader<Cursor> loader) { 407 // Nothing to do here. The Cursor does not need to be released as it was never directly 408 // bound to anything (like an adapter). 409 } 410 411 /** 412 * Builds an empty address layout that just shows that no addresses 413 * were found for this contact. 414 * 415 * @return A LinearLayout to add to the contact details layout 416 */ buildEmptyAddressLayout()417 private LinearLayout buildEmptyAddressLayout() { 418 return buildAddressLayout(0, null, null); 419 } 420 421 /** 422 * Builds an address LinearLayout based on address information from the Contacts Provider. 423 * Each address for the contact gets its own LinearLayout object; for example, if the contact 424 * has three postal addresses, then 3 LinearLayouts are generated. 425 * 426 * @param addressType From 427 * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#TYPE} 428 * @param addressTypeLabel From 429 * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#LABEL} 430 * @param address From 431 * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#FORMATTED_ADDRESS} 432 * @return A LinearLayout to add to the contact details layout, 433 * populated with the provided address details. 434 */ buildAddressLayout(int addressType, String addressTypeLabel, final String address)435 private LinearLayout buildAddressLayout(int addressType, String addressTypeLabel, 436 final String address) { 437 438 // Inflates the address layout 439 final LinearLayout addressLayout = 440 (LinearLayout) LayoutInflater.from(getActivity()).inflate( 441 R.layout.contact_detail_item, mDetailsLayout, false); 442 443 // Gets handles to the view objects in the layout 444 final TextView headerTextView = 445 (TextView) addressLayout.findViewById(R.id.contact_detail_header); 446 final TextView addressTextView = 447 (TextView) addressLayout.findViewById(R.id.contact_detail_item); 448 final ImageButton viewAddressButton = 449 (ImageButton) addressLayout.findViewById(R.id.button_view_address); 450 451 // If there's no addresses for the contact, shows the empty view and message, and hides the 452 // header and button. 453 if (addressTypeLabel == null && addressType == 0) { 454 headerTextView.setVisibility(View.GONE); 455 viewAddressButton.setVisibility(View.GONE); 456 addressTextView.setText(R.string.no_address); 457 } else { 458 // Gets postal address label type 459 CharSequence label = 460 StructuredPostal.getTypeLabel(getResources(), addressType, addressTypeLabel); 461 462 // Sets TextView objects in the layout 463 headerTextView.setText(label); 464 addressTextView.setText(address); 465 466 // Defines an onClickListener object for the address button 467 viewAddressButton.setOnClickListener(new View.OnClickListener() { 468 // Defines what to do when users click the address button 469 @Override 470 public void onClick(View view) { 471 472 final Intent viewIntent = 473 new Intent(Intent.ACTION_VIEW, constructGeoUri(address)); 474 475 // A PackageManager instance is needed to verify that there's a default app 476 // that handles ACTION_VIEW and a geo Uri. 477 final PackageManager packageManager = getActivity().getPackageManager(); 478 479 // Checks for an activity that can handle this intent. Preferred in this 480 // case over Intent.createChooser() as it will still let the user choose 481 // a default (or use a previously set default) for geo Uris. 482 if (packageManager.resolveActivity( 483 viewIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) { 484 startActivity(viewIntent); 485 } else { 486 // If no default is found, displays a message that no activity can handle 487 // the view button. 488 Toast.makeText(getActivity(), 489 R.string.no_intent_found, Toast.LENGTH_SHORT).show(); 490 } 491 } 492 }); 493 494 } 495 return addressLayout; 496 } 497 498 /** 499 * Constructs a geo scheme Uri from a postal address. 500 * 501 * @param postalAddress A postal address. 502 * @return the geo:// Uri for the postal address. 503 */ constructGeoUri(String postalAddress)504 private Uri constructGeoUri(String postalAddress) { 505 // Concatenates the geo:// prefix to the postal address. The postal address must be 506 // converted to Uri format and encoded for special characters. 507 return Uri.parse(GEO_URI_SCHEME_PREFIX + Uri.encode(postalAddress)); 508 } 509 510 /** 511 * Fetches the width or height of the screen in pixels, whichever is larger. This is used to 512 * set a maximum size limit on the contact photo that is retrieved from the Contacts Provider. 513 * This limit prevents the app from trying to decode and load an image that is much larger than 514 * the available screen area. 515 * 516 * @return The largest screen dimension in pixels. 517 */ getLargestScreenDimension()518 private int getLargestScreenDimension() { 519 // Gets a DisplayMetrics object, which is used to retrieve the display's pixel height and 520 // width 521 final DisplayMetrics displayMetrics = new DisplayMetrics(); 522 523 // Retrieves a displayMetrics object for the device's default display 524 getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); 525 final int height = displayMetrics.heightPixels; 526 final int width = displayMetrics.widthPixels; 527 528 // Returns the larger of the two values 529 return height > width ? height : width; 530 } 531 532 /** 533 * Decodes and returns the contact's thumbnail image. 534 * @param contactUri The Uri of the contact containing the image. 535 * @param imageSize The desired target width and height of the output image in pixels. 536 * @return If a thumbnail image exists for the contact, a Bitmap image, otherwise null. 537 */ 538 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) loadContactPhoto(Uri contactUri, int imageSize)539 private Bitmap loadContactPhoto(Uri contactUri, int imageSize) { 540 541 // Ensures the Fragment is still added to an activity. As this method is called in a 542 // background thread, there's the possibility the Fragment is no longer attached and 543 // added to an activity. If so, no need to spend resources loading the contact photo. 544 if (!isAdded() || getActivity() == null) { 545 return null; 546 } 547 548 // Instantiates a ContentResolver for retrieving the Uri of the image 549 final ContentResolver contentResolver = getActivity().getContentResolver(); 550 551 // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the 552 // ContentResolver can return an AssetFileDescriptor for the file. 553 AssetFileDescriptor afd = null; 554 555 if (Utils.hasICS()) { 556 // On platforms running Android 4.0 (API version 14) and later, a high resolution image 557 // is available from Photo.DISPLAY_PHOTO. 558 try { 559 // Constructs the content Uri for the image 560 Uri displayImageUri = Uri.withAppendedPath(contactUri, Photo.DISPLAY_PHOTO); 561 562 // Retrieves an AssetFileDescriptor from the Contacts Provider, using the 563 // constructed Uri 564 afd = contentResolver.openAssetFileDescriptor(displayImageUri, "r"); 565 // If the file exists 566 if (afd != null) { 567 // Reads and decodes the file to a Bitmap and scales it to the desired size 568 return ImageLoader.decodeSampledBitmapFromDescriptor( 569 afd.getFileDescriptor(), imageSize, imageSize); 570 } 571 } catch (FileNotFoundException e) { 572 // Catches file not found exceptions 573 if (BuildConfig.DEBUG) { 574 // Log debug message, this is not an error message as this exception is thrown 575 // when a contact is legitimately missing a contact photo (which will be quite 576 // frequently in a long contacts list). 577 Log.d(TAG, "Contact photo not found for contact " + contactUri.toString() 578 + ": " + e.toString()); 579 } 580 } finally { 581 // Once the decode is complete, this closes the file. You must do this each time 582 // you access an AssetFileDescriptor; otherwise, every image load you do will open 583 // a new descriptor. 584 if (afd != null) { 585 try { 586 afd.close(); 587 } catch (IOException e) { 588 // Closing a file descriptor might cause an IOException if the file is 589 // already closed. Nothing extra is needed to handle this. 590 } 591 } 592 } 593 } 594 595 // If the platform version is less than Android 4.0 (API Level 14), use the only available 596 // image URI, which points to a normal-sized image. 597 try { 598 // Constructs the image Uri from the contact Uri and the directory twig from the 599 // Contacts.Photo table 600 Uri imageUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY); 601 602 // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed 603 // Uri 604 afd = getActivity().getContentResolver().openAssetFileDescriptor(imageUri, "r"); 605 606 // If the file exists 607 if (afd != null) { 608 // Reads the image from the file, decodes it, and scales it to the available screen 609 // area 610 return ImageLoader.decodeSampledBitmapFromDescriptor( 611 afd.getFileDescriptor(), imageSize, imageSize); 612 } 613 } catch (FileNotFoundException e) { 614 // Catches file not found exceptions 615 if (BuildConfig.DEBUG) { 616 // Log debug message, this is not an error message as this exception is thrown 617 // when a contact is legitimately missing a contact photo (which will be quite 618 // frequently in a long contacts list). 619 Log.d(TAG, "Contact photo not found for contact " + contactUri.toString() 620 + ": " + e.toString()); 621 } 622 } finally { 623 // Once the decode is complete, this closes the file. You must do this each time you 624 // access an AssetFileDescriptor; otherwise, every image load you do will open a new 625 // descriptor. 626 if (afd != null) { 627 try { 628 afd.close(); 629 } catch (IOException e) { 630 // Closing a file descriptor might cause an IOException if the file is 631 // already closed. Ignore this. 632 } 633 } 634 } 635 636 // If none of the case selectors match, returns null. 637 return null; 638 } 639 640 /** 641 * This interface defines constants used by contact retrieval queries. 642 */ 643 public interface ContactDetailQuery { 644 // A unique query ID to distinguish queries being run by the 645 // LoaderManager. 646 final static int QUERY_ID = 1; 647 648 // The query projection (columns to fetch from the provider) 649 @SuppressLint("InlinedApi") 650 final static String[] PROJECTION = { 651 Contacts._ID, 652 Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME, 653 }; 654 655 // The query column numbers which map to each value in the projection 656 final static int ID = 0; 657 final static int DISPLAY_NAME = 1; 658 } 659 660 /** 661 * This interface defines constants used by address retrieval queries. 662 */ 663 public interface ContactAddressQuery { 664 // A unique query ID to distinguish queries being run by the 665 // LoaderManager. 666 final static int QUERY_ID = 2; 667 668 // The query projection (columns to fetch from the provider) 669 final static String[] PROJECTION = { 670 StructuredPostal._ID, 671 StructuredPostal.FORMATTED_ADDRESS, 672 StructuredPostal.TYPE, 673 StructuredPostal.LABEL, 674 }; 675 676 // The query selection criteria. In this case matching against the 677 // StructuredPostal content mime type. 678 final static String SELECTION = 679 Data.MIMETYPE + "='" + StructuredPostal.CONTENT_ITEM_TYPE + "'"; 680 681 // The query column numbers which map to each value in the projection 682 final static int ID = 0; 683 final static int ADDRESS = 1; 684 final static int TYPE = 2; 685 final static int LABEL = 3; 686 } 687 } 688