blob: 3802e77afa86be9effb76a70d092ed4301ee9b94 [file] [log] [blame]
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001/*
2 * Copyright (C) 2010 Google Inc.
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
17package com.android.contacts.views.detail;
18
19import com.android.contacts.Collapser;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070020import com.android.contacts.ContactOptionsActivity;
21import com.android.contacts.ContactPresenceIconUtil;
22import com.android.contacts.ContactsUtils;
23import com.android.contacts.R;
24import com.android.contacts.TypePrecedence;
25import com.android.contacts.Collapser.Collapsible;
26import com.android.contacts.model.ContactsSource;
27import com.android.contacts.model.Sources;
28import com.android.contacts.model.ContactsSource.DataKind;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070029import com.android.contacts.util.Constants;
30import com.android.contacts.util.DataStatus;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070031import com.android.contacts.views.ContactLoader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070032import com.android.internal.telephony.ITelephony;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070033
Daniel Lehmann25a02822010-05-11 15:52:43 -070034import android.app.Activity;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070035import android.app.AlertDialog;
36import android.app.Dialog;
Jeff Hamilton3c462912010-05-15 02:20:01 -050037import android.app.LoaderManagingFragment;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070038import android.content.ActivityNotFoundException;
39import android.content.ContentUris;
40import android.content.ContentValues;
41import android.content.Context;
42import android.content.DialogInterface;
43import android.content.Entity;
44import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050045import android.content.Loader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070046import android.content.Entity.NamedContentValues;
47import android.content.res.Resources;
48import android.graphics.drawable.Drawable;
49import android.net.ParseException;
50import android.net.Uri;
51import android.net.WebAddress;
52import android.os.Bundle;
53import android.os.RemoteException;
54import android.os.ServiceManager;
55import android.provider.ContactsContract.CommonDataKinds;
56import android.provider.ContactsContract.Contacts;
57import android.provider.ContactsContract.Data;
58import android.provider.ContactsContract.DisplayNameSources;
59import android.provider.ContactsContract.RawContacts;
60import android.provider.ContactsContract.StatusUpdates;
61import android.provider.ContactsContract.CommonDataKinds.Email;
62import android.provider.ContactsContract.CommonDataKinds.Im;
63import android.provider.ContactsContract.CommonDataKinds.Nickname;
64import android.provider.ContactsContract.CommonDataKinds.Note;
65import android.provider.ContactsContract.CommonDataKinds.Organization;
66import android.provider.ContactsContract.CommonDataKinds.Phone;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070067import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070068import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
69import android.provider.ContactsContract.CommonDataKinds.Website;
70import android.telephony.PhoneNumberUtils;
71import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070072import android.util.Log;
73import android.view.ContextMenu;
74import android.view.KeyEvent;
75import android.view.LayoutInflater;
76import android.view.Menu;
77import android.view.MenuInflater;
78import android.view.MenuItem;
79import android.view.View;
80import android.view.ViewGroup;
81import android.view.ContextMenu.ContextMenuInfo;
Daniel Lehmannc2687c32010-04-19 18:20:44 -070082import android.view.View.OnClickListener;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070083import android.view.View.OnCreateContextMenuListener;
84import android.widget.AdapterView;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070085import android.widget.BaseAdapter;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070086import android.widget.ImageView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087import android.widget.ListView;
88import android.widget.TextView;
89import android.widget.Toast;
90import android.widget.AdapterView.OnItemClickListener;
91
92import java.util.ArrayList;
93
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070094public class ContactDetailFragment extends LoaderManagingFragment<ContactLoader.Result>
Daniel Lehmann18f104f2010-05-07 15:41:11 -070095 implements OnCreateContextMenuListener, OnItemClickListener {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070096 private static final String TAG = "ContactDetailFragment";
Daniel Lehmann4cd94412010-04-08 16:44:36 -070097
Dmitri Plotnikovd3643142010-07-09 13:49:07 -070098 private static final String KEY_LOOKUP_URI = "lookupUri";
99
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700100 private static final int MENU_ITEM_MAKE_DEFAULT = 3;
101
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700102 private static final int LOADER_DETAILS = 1;
103
Daniel Lehmann25a02822010-05-11 15:52:43 -0700104 private Context mContext;
105 private Uri mLookupUri;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700106 private Listener mListener;
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700107
Daniel Lehmanncc8bfc82010-06-21 19:46:53 -0700108 private boolean mIsInitialized;
109
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700110 private ContactLoader.Result mContactData;
111 private ContactDetailHeaderView mHeaderView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700112 private ListView mListView;
113 private boolean mShowSmsLinksForAllPhones;
114 private ViewAdapter mAdapter;
115 private Uri mPrimaryPhoneUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700116
117 private int mReadOnlySourcesCnt;
118 private int mWritableSourcesCnt;
119 private boolean mAllRestricted;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700120 private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700121 private int mNumPhoneNumbers = 0;
122
123 /**
124 * The view shown if the detail list is empty.
125 * We set this to the list view when first bind the adapter, so that it won't be shown while
126 * we're loading data.
127 */
128 private View mEmptyView;
129
130 /**
131 * A list of distinct contact IDs included in the current contact.
132 */
133 private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
134 private ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
135 private ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
136 private ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
137 private ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
138 private ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
139 private ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
140 private ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
141 private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
142 private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
143 private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700144 private LayoutInflater mInflater;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700145
Daniel Lehmann25a02822010-05-11 15:52:43 -0700146 public ContactDetailFragment() {
147 // Explicit constructor for inflation
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700148
149 // Build the list of sections. The order they're added to mSections dictates the
150 // order they are displayed in the list.
151 mSections.add(mPhoneEntries);
152 mSections.add(mSmsEntries);
153 mSections.add(mEmailEntries);
154 mSections.add(mImEntries);
155 mSections.add(mPostalEntries);
156 mSections.add(mNicknameEntries);
157 mSections.add(mOrganizationEntries);
158 mSections.add(mGroupEntries);
159 mSections.add(mOtherEntries);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700160 }
161
162 @Override
163 public void onAttach(Activity activity) {
164 super.onAttach(activity);
165 mContext = activity;
166 }
167
168 @Override
Dmitri Plotnikovd3643142010-07-09 13:49:07 -0700169 public void onCreate(Bundle savedState) {
170 super.onCreate(savedState);
171
172 if (savedState != null) {
173 mLookupUri = savedState.getParcelable(KEY_LOOKUP_URI);
174 if (mLookupUri != null) {
175 loadUri(mLookupUri);
176 }
177 }
178 }
179
180 @Override
181 public void onSaveInstanceState(Bundle outState) {
182 super.onSaveInstanceState(outState);
183 outState.putParcelable(KEY_LOOKUP_URI, mLookupUri);
184 }
185
186 @Override
Dianne Hackborn84df2b62010-05-12 19:04:42 -0700187 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700188 final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700189
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700190 setHasOptionsMenu(true);
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700191
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700192 mInflater = inflater;
193
194 mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700195 mHeaderView.setExcludeMimes(new String[] {
Daniel Lehmann25a02822010-05-11 15:52:43 -0700196 Contacts.CONTENT_ITEM_TYPE
197 });
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700198 mHeaderView.setListener(mHeaderViewListener);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700199
200 mListView = (ListView) view.findViewById(android.R.id.list);
201 mListView.setOnCreateContextMenuListener(this);
202 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
203 mListView.setOnItemClickListener(this);
204 // Don't set it to mListView yet. We do so later when we bind the adapter.
205 mEmptyView = view.findViewById(android.R.id.empty);
206
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700207 //TODO Read this value from a preference
208 mShowSmsLinksForAllPhones = true;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700209
Daniel Lehmann25a02822010-05-11 15:52:43 -0700210 return view;
211 }
212
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700213 public void setListener(Listener value) {
214 mListener = value;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700215 }
216
Dmitri Plotnikov9cd17082010-07-09 14:10:04 -0700217 public Uri getUri() {
218 return mLookupUri;
219 }
220
Daniel Lehmann25a02822010-05-11 15:52:43 -0700221 public void loadUri(Uri lookupUri) {
Daniel Lehmannf319cf82010-06-25 17:41:13 -0700222 // TODO: Ensure we are not loading twice here
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700223 mLookupUri = lookupUri;
Daniel Lehmanncc8bfc82010-06-21 19:46:53 -0700224 if (mIsInitialized) startLoading(LOADER_DETAILS, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700225 }
226
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700227 @Override
228 protected void onInitializeLoaders() {
Daniel Lehmanncc8bfc82010-06-21 19:46:53 -0700229 mIsInitialized = true;
230 if (mLookupUri != null) startLoading(LOADER_DETAILS, null);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700231 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700232
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700233 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700234 protected Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700235 switch (id) {
236 case LOADER_DETAILS: {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700237 return new ContactLoader(mContext, mLookupUri);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700238 }
239 default: {
240 Log.wtf(TAG, "Unknown ID in onCreateLoader: " + id);
241 }
242 }
243 return null;
244 }
245
246 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700247 protected void onLoadFinished(Loader<ContactLoader.Result> loader,
248 ContactLoader.Result data) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700249 final int id = loader.getId();
250 switch (id) {
251 case LOADER_DETAILS:
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700252 if (data == ContactLoader.Result.NOT_FOUND) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700253 // Item has been deleted
254 Log.i(TAG, "No contact found. Closing activity");
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700255 mListener.onContactNotFound();
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700256 return;
257 }
258 mContactData = data;
259 bindData();
260 break;
261 default: {
262 Log.wtf(TAG, "Unknown ID in onLoadFinished: " + id);
263 }
264 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700265 }
266
267 private void bindData() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700268 // Set the header
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700269 mHeaderView.loadData(mContactData);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700270
271 // Build up the contact entries
272 buildEntries();
273
274 // Collapse similar data items in select sections.
275 Collapser.collapseList(mPhoneEntries);
276 Collapser.collapseList(mSmsEntries);
277 Collapser.collapseList(mEmailEntries);
278 Collapser.collapseList(mPostalEntries);
279 Collapser.collapseList(mImEntries);
280
281 if (mAdapter == null) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700282 mAdapter = new ViewAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700283 mListView.setAdapter(mAdapter);
284 } else {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700285 mAdapter.notifyDataSetChanged();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700286 }
287 mListView.setEmptyView(mEmptyView);
288 }
289
290 /**
291 * Build up the entries to display on the screen.
292 */
293 private final void buildEntries() {
294 // Clear out the old entries
295 final int numSections = mSections.size();
296 for (int i = 0; i < numSections; i++) {
297 mSections.get(i).clear();
298 }
299
300 mRawContactIds.clear();
301
302 mReadOnlySourcesCnt = 0;
303 mWritableSourcesCnt = 0;
304 mAllRestricted = true;
305 mPrimaryPhoneUri = null;
306 mNumPhoneNumbers = 0;
307
308 mWritableRawContactIds.clear();
309
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700310 // TODO: This should be done in the background thread
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700311 final Sources sources = Sources.getInstance(mContext);
312
313 // Build up method entries
314 if (mContactData == null) {
315 return;
316 }
317
318 for (Entity entity: mContactData.getEntities()) {
319 final ContentValues entValues = entity.getEntityValues();
320 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
321 final long rawContactId = entValues.getAsLong(RawContacts._ID);
322
323 // Mark when this contact has any unrestricted components
324 final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0;
325 if (!isRestricted) mAllRestricted = false;
326
327 if (!mRawContactIds.contains(rawContactId)) {
328 mRawContactIds.add(rawContactId);
329 }
330 ContactsSource contactsSource = sources.getInflatedSource(accountType,
331 ContactsSource.LEVEL_SUMMARY);
332 if (contactsSource != null && contactsSource.readOnly) {
333 mReadOnlySourcesCnt += 1;
334 } else {
335 mWritableSourcesCnt += 1;
336 mWritableRawContactIds.add(rawContactId);
337 }
338
339
340 for (NamedContentValues subValue : entity.getSubValues()) {
341 final ContentValues entryValues = subValue.values;
342 entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
343
344 final long dataId = entryValues.getAsLong(Data._ID);
345 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
346 if (mimeType == null) continue;
347
348 final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
349 ContactsSource.LEVEL_MIMETYPES);
350 if (kind == null) continue;
351
352 final ViewEntry entry = ViewEntry.fromValues(mContext, mimeType, kind,
353 rawContactId, dataId, entryValues);
354
355 final boolean hasData = !TextUtils.isEmpty(entry.data);
356 final boolean isSuperPrimary = entryValues.getAsInteger(
357 Data.IS_SUPER_PRIMARY) != 0;
358
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700359 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
360 // Always ignore the name. It is shown in the header if set
361 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700362 // Build phone entries
363 mNumPhoneNumbers++;
364
365 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
366 Uri.fromParts(Constants.SCHEME_TEL, entry.data, null));
367 entry.secondaryIntent = new Intent(Intent.ACTION_SENDTO,
368 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null));
369
370 // Remember super-primary phone
371 if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
372
373 entry.isPrimary = isSuperPrimary;
374 mPhoneEntries.add(entry);
375
376 if (entry.type == CommonDataKinds.Phone.TYPE_MOBILE
377 || mShowSmsLinksForAllPhones) {
378 // Add an SMS entry
379 if (kind.iconAltRes > 0) {
380 entry.secondaryActionIcon = kind.iconAltRes;
381 }
382 }
383 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
384 // Build email entries
385 entry.intent = new Intent(Intent.ACTION_SENDTO,
386 Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
387 entry.isPrimary = isSuperPrimary;
388 mEmailEntries.add(entry);
389
390 // When Email rows have status, create additional Im row
391 final DataStatus status = mContactData.getStatuses().get(entry.id);
392 if (status != null) {
393 final String imMime = Im.CONTENT_ITEM_TYPE;
394 final DataKind imKind = sources.getKindOrFallback(accountType,
395 imMime, mContext, ContactsSource.LEVEL_MIMETYPES);
396 final ViewEntry imEntry = ViewEntry.fromValues(mContext,
397 imMime, imKind, rawContactId, dataId, entryValues);
398 imEntry.intent = ContactsUtils.buildImIntent(entryValues);
399 imEntry.applyStatus(status, false);
400 mImEntries.add(imEntry);
401 }
402 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
403 // Build postal entries
404 entry.maxLines = 4;
405 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
406 mPostalEntries.add(entry);
407 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
408 // Build IM entries
409 entry.intent = ContactsUtils.buildImIntent(entryValues);
410 if (TextUtils.isEmpty(entry.label)) {
411 entry.label = mContext.getString(R.string.chat).toLowerCase();
412 }
413
414 // Apply presence and status details when available
415 final DataStatus status = mContactData.getStatuses().get(entry.id);
416 if (status != null) {
417 entry.applyStatus(status, false);
418 }
419 mImEntries.add(entry);
420 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
421 (hasData || !TextUtils.isEmpty(entry.label))) {
422 // Build organization entries
423 final boolean isNameRawContact =
424 (mContactData.getNameRawContactId() == rawContactId);
425
426 final boolean duplicatesTitle =
427 isNameRawContact
428 && mContactData.getDisplayNameSource()
429 == DisplayNameSources.ORGANIZATION
430 && (!hasData || TextUtils.isEmpty(entry.label));
431
432 if (!duplicatesTitle) {
433 entry.uri = null;
434
435 if (TextUtils.isEmpty(entry.label)) {
436 entry.label = entry.data;
437 entry.data = "";
438 }
439
440 mOrganizationEntries.add(entry);
441 }
442 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
443 // Build nickname entries
444 final boolean isNameRawContact =
445 (mContactData.getNameRawContactId() == rawContactId);
446
447 final boolean duplicatesTitle =
448 isNameRawContact
449 && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
450
451 if (!duplicatesTitle) {
452 entry.uri = null;
453 mNicknameEntries.add(entry);
454 }
455 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
456 // Build note entries
457 entry.uri = null;
458 entry.maxLines = 100;
459 mOtherEntries.add(entry);
460 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
461 // Build note entries
462 entry.uri = null;
463 entry.maxLines = 10;
464 try {
465 WebAddress webAddress = new WebAddress(entry.data);
466 entry.intent = new Intent(Intent.ACTION_VIEW,
467 Uri.parse(webAddress.toString()));
468 } catch (ParseException e) {
469 Log.e(TAG, "Couldn't parse website: " + entry.data);
470 }
471 mOtherEntries.add(entry);
472 } else {
473 // Handle showing custom rows
474 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
475
476 // Use social summary when requested by external source
477 final DataStatus status = mContactData.getStatuses().get(entry.id);
478 final boolean hasSocial = kind.actionBodySocial && status != null;
479 if (hasSocial) {
480 entry.applyStatus(status, true);
481 }
482
483 if (hasSocial || hasData) {
484 mOtherEntries.add(entry);
485 }
486 }
487 }
488 }
489 }
490
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700491 private static String buildActionString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700492 boolean lowerCase, Context context) {
493 if (kind.actionHeader == null) {
494 return null;
495 }
496 CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
497 if (actionHeader == null) {
498 return null;
499 }
500 return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
501 }
502
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700503 private static String buildDataString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700504 Context context) {
505 if (kind.actionBody == null) {
506 return null;
507 }
508 CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
509 return actionBody == null ? null : actionBody.toString();
510 }
511
512 /**
513 * A basic structure with the data for a contact entry in the list.
514 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700515 private static class ViewEntry implements Collapsible<ViewEntry> {
516 // Copied from baseclass
517 public int type = -1;
518 public String label;
519 public String data;
520 public Uri uri;
521 public long id = 0;
522 public int maxLines = 1;
523 public String mimetype;
524
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700525 public Context context = null;
526 public String resPackageName = null;
527 public int actionIcon = -1;
528 public boolean isPrimary = false;
529 public int secondaryActionIcon = -1;
530 public Intent intent;
531 public Intent secondaryIntent = null;
532 public int maxLabelLines = 1;
533 public ArrayList<Long> ids = new ArrayList<Long>();
534 public int collapseCount = 0;
535
536 public int presence = -1;
537
538 public CharSequence footerLine = null;
539
540 private ViewEntry() {
541 }
542
543 /**
544 * Build new {@link ViewEntry} and populate from the given values.
545 */
546 public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
547 long rawContactId, long dataId, ContentValues values) {
548 final ViewEntry entry = new ViewEntry();
549 entry.context = context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700550 entry.id = dataId;
551 entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
552 entry.mimetype = mimeType;
553 entry.label = buildActionString(kind, values, false, context);
554 entry.data = buildDataString(kind, values, context);
555
556 if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
557 entry.type = values.getAsInteger(kind.typeColumn);
558 }
559 if (kind.iconRes > 0) {
560 entry.resPackageName = kind.resPackageName;
561 entry.actionIcon = kind.iconRes;
562 }
563
564 return entry;
565 }
566
567 /**
568 * Apply given {@link DataStatus} values over this {@link ViewEntry}
569 *
570 * @param fillData When true, the given status replaces {@link #data}
571 * and {@link #footerLine}. Otherwise only {@link #presence}
572 * is updated.
573 */
574 public ViewEntry applyStatus(DataStatus status, boolean fillData) {
575 presence = status.getPresence();
576 if (fillData && status.isValid()) {
577 this.data = status.getStatus().toString();
578 this.footerLine = status.getTimestampLabel(context);
579 }
580
581 return this;
582 }
583
584 public boolean collapseWith(ViewEntry entry) {
585 // assert equal collapse keys
586 if (!shouldCollapseWith(entry)) {
587 return false;
588 }
589
590 // Choose the label associated with the highest type precedence.
591 if (TypePrecedence.getTypePrecedence(mimetype, type)
592 > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
593 type = entry.type;
594 label = entry.label;
595 }
596
597 // Choose the max of the maxLines and maxLabelLines values.
598 maxLines = Math.max(maxLines, entry.maxLines);
599 maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines);
600
601 // Choose the presence with the highest precedence.
602 if (StatusUpdates.getPresencePrecedence(presence)
603 < StatusUpdates.getPresencePrecedence(entry.presence)) {
604 presence = entry.presence;
605 }
606
607 // If any of the collapsed entries are primary make the whole thing primary.
608 isPrimary = entry.isPrimary ? true : isPrimary;
609
610 // uri, and contactdId, shouldn't make a difference. Just keep the original.
611
612 // Keep track of all the ids that have been collapsed with this one.
613 ids.add(entry.id);
614 collapseCount++;
615 return true;
616 }
617
618 public boolean shouldCollapseWith(ViewEntry entry) {
619 if (entry == null) {
620 return false;
621 }
622
623 if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
624 entry.data)) {
625 return false;
626 }
627
628 if (!TextUtils.equals(mimetype, entry.mimetype)
629 || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
630 || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
631 || actionIcon != entry.actionIcon) {
632 return false;
633 }
634
635 return true;
636 }
637 }
638
639 /** Cache of the children views of a row */
640 private static class ViewCache {
641 public TextView label;
642 public TextView data;
643 public TextView footer;
644 public ImageView actionIcon;
645 public ImageView presenceIcon;
646 public ImageView primaryIcon;
647 public ImageView secondaryActionButton;
648 public View secondaryActionDivider;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700649 }
650
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700651 private final class ViewAdapter extends BaseAdapter {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700652 public View getView(int position, View convertView, ViewGroup parent) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700653 final ViewEntry entry = getEntry(position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700654 final View v;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700655 final ViewCache viewCache;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700656
657 // Check to see if we can reuse convertView
658 if (convertView != null) {
659 v = convertView;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700660 viewCache = (ViewCache) v.getTag();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700661 } else {
662 // Create a new view if needed
663 v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
664
665 // Cache the children
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700666 viewCache = new ViewCache();
667 viewCache.label = (TextView) v.findViewById(android.R.id.text1);
668 viewCache.data = (TextView) v.findViewById(android.R.id.text2);
669 viewCache.footer = (TextView) v.findViewById(R.id.footer);
670 viewCache.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
671 viewCache.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
672 viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
673 viewCache.secondaryActionButton = (ImageView) v.findViewById(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700674 R.id.secondary_action_button);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700675 viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700676 viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
677 v.setTag(viewCache);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700678 }
679
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700680 // Bind the data to the view
681 bindView(v, entry);
682 return v;
683 }
684
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700685 protected void bindView(View view, ViewEntry entry) {
686 final Resources resources = mContext.getResources();
687 ViewCache views = (ViewCache) view.getTag();
688
689 // Set the label
690 TextView label = views.label;
691 setMaxLines(label, entry.maxLabelLines);
692 label.setText(entry.label);
693
694 // Set the data
695 TextView data = views.data;
696 if (data != null) {
697 if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE)
698 || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
699 data.setText(PhoneNumberUtils.formatNumber(entry.data));
700 } else {
701 data.setText(entry.data);
702 }
703 setMaxLines(data, entry.maxLines);
704 }
705
706 // Set the footer
707 if (!TextUtils.isEmpty(entry.footerLine)) {
708 views.footer.setText(entry.footerLine);
709 views.footer.setVisibility(View.VISIBLE);
710 } else {
711 views.footer.setVisibility(View.GONE);
712 }
713
714 // Set the primary icon
715 views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
716
717 // Set the action icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700718 final ImageView action = views.actionIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700719 if (entry.actionIcon != -1) {
720 Drawable actionIcon;
721 if (entry.resPackageName != null) {
722 // Load external resources through PackageManager
723 actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
724 entry.actionIcon, null);
725 } else {
726 actionIcon = resources.getDrawable(entry.actionIcon);
727 }
728 action.setImageDrawable(actionIcon);
729 action.setVisibility(View.VISIBLE);
730 } else {
731 // Things should still line up as if there was an icon, so make it invisible
732 action.setVisibility(View.INVISIBLE);
733 }
734
735 // Set the presence icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700736 final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700737 mContext, entry.presence);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700738 final ImageView presenceIconView = views.presenceIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700739 if (presenceIcon != null) {
740 presenceIconView.setImageDrawable(presenceIcon);
741 presenceIconView.setVisibility(View.VISIBLE);
742 } else {
743 presenceIconView.setVisibility(View.GONE);
744 }
745
746 // Set the secondary action button
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700747 final ImageView secondaryActionView = views.secondaryActionButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700748 Drawable secondaryActionIcon = null;
749 if (entry.secondaryActionIcon != -1) {
750 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
751 }
752 if (entry.secondaryIntent != null && secondaryActionIcon != null) {
753 secondaryActionView.setImageDrawable(secondaryActionIcon);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700754 secondaryActionView.setTag(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700755 secondaryActionView.setVisibility(View.VISIBLE);
756 views.secondaryActionDivider.setVisibility(View.VISIBLE);
757 } else {
758 secondaryActionView.setVisibility(View.GONE);
759 views.secondaryActionDivider.setVisibility(View.GONE);
760 }
761 }
762
763 private void setMaxLines(TextView textView, int maxLines) {
764 if (maxLines == 1) {
765 textView.setSingleLine(true);
766 textView.setEllipsize(TextUtils.TruncateAt.END);
767 } else {
768 textView.setSingleLine(false);
769 textView.setMaxLines(maxLines);
770 textView.setEllipsize(null);
771 }
772 }
773
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700774 private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
775 public void onClick(View v) {
776 if (mListener == null) return;
777 if (v == null) return;
778 final ViewEntry entry = (ViewEntry) v.getTag();
779 if (entry == null) return;
780 final Intent intent = entry.secondaryIntent;
781 if (intent == null) return;
782 mListener.onItemClicked(intent);
783 }
784 };
785
786 public int getCount() {
787 int count = 0;
788 final int numSections = mSections.size();
789 for (int i = 0; i < numSections; i++) {
790 final ArrayList<ViewEntry> section = mSections.get(i);
791 count += section.size();
792 }
793 return count;
794 }
795
796 public Object getItem(int position) {
797 return getEntry(position);
798 }
799
800 public long getItemId(int position) {
801 final ViewEntry entry = getEntry(position);
802 if (entry != null) {
803 return entry.id;
804 } else {
805 return -1;
806 }
807 }
808
809 private ViewEntry getEntry(int position) {
810 final int numSections = mSections.size();
811 for (int i = 0; i < numSections; i++) {
812 final ArrayList<ViewEntry> section = mSections.get(i);
813 final int sectionSize = section.size();
814 if (position < sectionSize) {
815 return section.get(position);
816 }
817 position -= sectionSize;
818 }
819 return null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700820 }
821 }
822
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200823 @Override
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700824 public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700825 inflater.inflate(R.menu.view, menu);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700826 }
827
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200828 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700829 public boolean onOptionsItemSelected(MenuItem item) {
830 switch (item.getItemId()) {
831 case R.id.menu_edit: {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700832 mListener.onEditRequested(mLookupUri);
833 break;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700834 }
835 case R.id.menu_delete: {
836 showDeleteConfirmationDialog();
837 return true;
838 }
839 case R.id.menu_options: {
840 final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
841 intent.setData(mContactData.getLookupUri());
842 mContext.startActivity(intent);
843 return true;
844 }
845 case R.id.menu_share: {
846 if (mAllRestricted) return false;
847
848 final String lookupKey = mContactData.getLookupKey();
849 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
850
851 final Intent intent = new Intent(Intent.ACTION_SEND);
852 intent.setType(Contacts.CONTENT_VCARD_TYPE);
853 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
854
855 // Launch chooser to share contact via
856 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
857 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
858
859 try {
860 mContext.startActivity(chooseIntent);
861 } catch (ActivityNotFoundException ex) {
862 Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
863 }
864 return true;
865 }
866 }
867 return false;
868 }
869
870 private void showDeleteConfirmationDialog() {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700871 final int id;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700872 if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700873 id = R.id.detail_dialog_confirm_readonly_delete;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700874 } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700875 id = R.id.detail_dialog_confirm_readonly_hide;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700876 } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700877 id = R.id.detail_dialog_confirm_multiple_delete;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700878 } else {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700879 id = R.id.detail_dialog_confirm_delete;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700880 }
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700881 if (mListener != null) mListener.onDialogRequested(id, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700882 }
883
Dmitri Plotnikovd3643142010-07-09 13:49:07 -0700884 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700885 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
886 AdapterView.AdapterContextMenuInfo info;
887 try {
888 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
889 } catch (ClassCastException e) {
890 Log.e(TAG, "bad menuInfo", e);
891 return;
892 }
893
894 // This can be null sometimes, don't crash...
895 if (info == null) {
896 Log.e(TAG, "bad menuInfo");
897 return;
898 }
899
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700900 final ViewEntry entry = mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700901 menu.setHeaderTitle(R.string.contactOptionsTitle);
902 if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
903 menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
904 menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.secondaryIntent);
905 if (!entry.isPrimary) {
906 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
907 }
908 } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
909 menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
910 if (!entry.isPrimary) {
911 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
912 }
913 } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
914 menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
915 }
916 }
917
918 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700919 if (mListener == null) return;
920 final ViewEntry entry = mAdapter.getEntry(position);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700921 if (entry == null) return;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700922 final Intent intent = entry.intent;
923 if (intent == null) return;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700924 mListener.onItemClicked(intent);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700925 }
926
927 private final DialogInterface.OnClickListener mDeleteListener =
928 new DialogInterface.OnClickListener() {
929 public void onClick(DialogInterface dialog, int which) {
930 mContext.getContentResolver().delete(mContactData.getLookupUri(), null, null);
931 }
932 };
933
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700934 public Dialog onCreateDialog(int id, Bundle bundle) {
935 switch (id) {
936 case R.id.detail_dialog_confirm_delete:
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700937 return new AlertDialog.Builder(mContext)
938 .setTitle(R.string.deleteConfirmation_title)
939 .setIcon(android.R.drawable.ic_dialog_alert)
940 .setMessage(R.string.deleteConfirmation)
941 .setNegativeButton(android.R.string.cancel, null)
942 .setPositiveButton(android.R.string.ok, mDeleteListener)
943 .setCancelable(false)
944 .create();
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700945 case R.id.detail_dialog_confirm_readonly_delete:
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700946 return new AlertDialog.Builder(mContext)
947 .setTitle(R.string.deleteConfirmation_title)
948 .setIcon(android.R.drawable.ic_dialog_alert)
949 .setMessage(R.string.readOnlyContactDeleteConfirmation)
950 .setNegativeButton(android.R.string.cancel, null)
951 .setPositiveButton(android.R.string.ok, mDeleteListener)
952 .setCancelable(false)
953 .create();
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700954 case R.id.detail_dialog_confirm_multiple_delete:
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700955 return new AlertDialog.Builder(mContext)
956 .setTitle(R.string.deleteConfirmation_title)
957 .setIcon(android.R.drawable.ic_dialog_alert)
958 .setMessage(R.string.multipleContactDeleteConfirmation)
959 .setNegativeButton(android.R.string.cancel, null)
960 .setPositiveButton(android.R.string.ok, mDeleteListener)
961 .setCancelable(false)
962 .create();
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700963 case R.id.detail_dialog_confirm_readonly_hide: {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700964 return new AlertDialog.Builder(mContext)
965 .setTitle(R.string.deleteConfirmation_title)
966 .setIcon(android.R.drawable.ic_dialog_alert)
967 .setMessage(R.string.readOnlyContactWarning)
968 .setPositiveButton(android.R.string.ok, mDeleteListener)
969 .create();
970 }
971 default:
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700972 return null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700973 }
974 }
975
Dmitri Plotnikovd3643142010-07-09 13:49:07 -0700976 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700977 public boolean onContextItemSelected(MenuItem item) {
978 switch (item.getItemId()) {
979 case MENU_ITEM_MAKE_DEFAULT: {
980 if (makeItemDefault(item)) {
981 return true;
982 }
983 break;
984 }
985 }
986
987 return false;
988 }
989
990 private boolean makeItemDefault(MenuItem item) {
991 ViewEntry entry = getViewEntryForMenuItem(item);
992 if (entry == null) {
993 return false;
994 }
995
996 // Update the primary values in the data record.
997 ContentValues values = new ContentValues(1);
998 values.put(Data.IS_SUPER_PRIMARY, 1);
999
1000 mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
1001 values, null, null);
1002 return true;
1003 }
1004
1005 private ViewEntry getViewEntryForMenuItem(MenuItem item) {
1006 AdapterView.AdapterContextMenuInfo info;
1007 try {
1008 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
1009 } catch (ClassCastException e) {
1010 Log.e(TAG, "bad menuInfo", e);
1011 return null;
1012 }
1013
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001014 return mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001015 }
1016
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001017 public boolean onKeyDown(int keyCode, KeyEvent event) {
1018 switch (keyCode) {
1019 case KeyEvent.KEYCODE_CALL: {
1020 try {
1021 ITelephony phone = ITelephony.Stub.asInterface(
1022 ServiceManager.checkService("phone"));
1023 if (phone != null && !phone.isIdle()) {
1024 // Skip out and let the key be handled at a higher level
1025 break;
1026 }
1027 } catch (RemoteException re) {
1028 // Fall through and try to call the contact
1029 }
1030
1031 int index = mListView.getSelectedItemPosition();
1032 if (index != -1) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001033 final ViewEntry entry = mAdapter.getEntry(index);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001034 if (entry != null &&
1035 entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
1036 mContext.startActivity(entry.intent);
1037 return true;
1038 }
1039 } else if (mPrimaryPhoneUri != null) {
1040 // There isn't anything selected, call the default number
1041 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1042 mPrimaryPhoneUri);
1043 mContext.startActivity(intent);
1044 return true;
1045 }
1046 return false;
1047 }
1048
1049 case KeyEvent.KEYCODE_DEL: {
1050 showDeleteConfirmationDialog();
1051 return true;
1052 }
1053 }
1054
Daniel Lehmannc2687c32010-04-19 18:20:44 -07001055 return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001056 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001057
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001058 private ContactDetailHeaderView.Listener mHeaderViewListener =
1059 new ContactDetailHeaderView.Listener() {
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001060 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001061 public void onDisplayNameClick(View view) {
1062 }
1063
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001064 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001065 public void onPhotoClick(View view) {
1066 }
1067 };
1068
1069
1070 public static interface Listener {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001071 /**
1072 * Contact was not found, so somehow close this fragment.
1073 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001074 public void onContactNotFound();
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001075
1076 /**
1077 * User decided to go to Edit-Mode
1078 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001079 public void onEditRequested(Uri lookupUri);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001080
1081 /**
1082 * User clicked a single item (e.g. mail)
1083 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001084 public void onItemClicked(Intent intent);
1085
1086 /**
1087 * Show a dialog using the globally unique id
1088 */
1089 public void onDialogRequested(int id, Bundle bundle);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001090 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001091}