1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
|
/*
* 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;
}
}
|