blob: c734f84b1fc673e2d641109558b4138027aa5e33 [file] [log] [blame]
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001/*
Daniel Lehmannef3f8f02010-07-26 18:55:25 -07002 * Copyright (C) 2010 The Android Open Source Project
Daniel Lehmann4cd94412010-04-08 16:44:36 -07003 *
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;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070020import com.android.contacts.Collapser.Collapsible;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070021import com.android.contacts.ContactOptionsActivity;
22import com.android.contacts.ContactPresenceIconUtil;
23import com.android.contacts.ContactsUtils;
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -070024import com.android.contacts.ContactsUtils.ImActions;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070025import com.android.contacts.R;
26import com.android.contacts.TypePrecedence;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070027import com.android.contacts.model.ContactsSource;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070028import com.android.contacts.model.ContactsSource.DataKind;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070029import com.android.contacts.model.Sources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070030import com.android.contacts.util.Constants;
31import com.android.contacts.util.DataStatus;
Daniel Lehmannf77b8e92010-07-16 18:58:36 -070032import com.android.contacts.util.PhoneCapabilityTester;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070033import com.android.contacts.views.ContactLoader;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070034import com.android.contacts.views.editor.SelectAccountDialogFragment;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070035import com.android.internal.telephony.ITelephony;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070036
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070037import android.accounts.Account;
Daniel Lehmann25a02822010-05-11 15:52:43 -070038import android.app.Activity;
Daniel Lehmann222744e2010-07-12 16:28:06 -070039import android.app.Fragment;
40import android.app.LoaderManager;
41import android.app.LoaderManager.LoaderCallbacks;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070042import android.content.ActivityNotFoundException;
43import android.content.ContentUris;
44import android.content.ContentValues;
45import android.content.Context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070046import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070047import android.content.Entity.NamedContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070048import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050049import android.content.Loader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import android.content.res.Resources;
51import android.graphics.drawable.Drawable;
52import android.net.ParseException;
53import android.net.Uri;
54import android.net.WebAddress;
55import android.os.Bundle;
56import android.os.RemoteException;
57import android.os.ServiceManager;
58import android.provider.ContactsContract.CommonDataKinds;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070059import android.provider.ContactsContract.CommonDataKinds.Email;
60import android.provider.ContactsContract.CommonDataKinds.Im;
61import android.provider.ContactsContract.CommonDataKinds.Nickname;
62import android.provider.ContactsContract.CommonDataKinds.Note;
63import android.provider.ContactsContract.CommonDataKinds.Organization;
64import android.provider.ContactsContract.CommonDataKinds.Phone;
David Brown4423ca52010-08-30 18:56:37 -070065import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070066import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070067import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
68import android.provider.ContactsContract.CommonDataKinds.Website;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070069import android.provider.ContactsContract.Contacts;
70import android.provider.ContactsContract.Data;
71import android.provider.ContactsContract.Directory;
72import android.provider.ContactsContract.DisplayNameSources;
73import android.provider.ContactsContract.RawContacts;
74import android.provider.ContactsContract.StatusUpdates;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070075import android.telephony.PhoneNumberUtils;
76import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070077import android.util.Log;
78import android.view.ContextMenu;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070079import android.view.ContextMenu.ContextMenuInfo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070080import android.view.KeyEvent;
81import android.view.LayoutInflater;
82import android.view.Menu;
83import android.view.MenuInflater;
84import android.view.MenuItem;
85import android.view.View;
Daniel Lehmannc2687c32010-04-19 18:20:44 -070086import android.view.View.OnClickListener;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087import android.view.View.OnCreateContextMenuListener;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070088import android.view.ViewGroup;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070089import android.widget.AdapterView;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070090import android.widget.AdapterView.OnItemClickListener;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070091import android.widget.BaseAdapter;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070092import android.widget.ImageView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070093import android.widget.ListView;
94import android.widget.TextView;
95import android.widget.Toast;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070096
97import java.util.ArrayList;
98
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070099public class ContactDetailFragment extends Fragment implements OnCreateContextMenuListener,
100 OnItemClickListener, SelectAccountDialogFragment.Listener {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700101 private static final String TAG = "ContactDetailFragment";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700102
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700103 private static final int MENU_ITEM_MAKE_DEFAULT = 3;
104
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700105 private static final int LOADER_DETAILS = 1;
106
Daniel Lehmann25a02822010-05-11 15:52:43 -0700107 private Context mContext;
108 private Uri mLookupUri;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700109 private Listener mListener;
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700110
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700111 private ContactLoader.Result mContactData;
112 private ContactDetailHeaderView mHeaderView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700113 private ListView mListView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700114 private ViewAdapter mAdapter;
115 private Uri mPrimaryPhoneUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700116
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700117 private boolean mAllRestricted;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700118 private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119 private int mNumPhoneNumbers = 0;
120
121 /**
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700122 * Device capability: Set during buildEntries and used in the long-press context menu
123 */
124 private boolean mHasPhone;
125
126 /**
127 * Device capability: Set during buildEntries and used in the long-press context menu
128 */
129 private boolean mHasSms;
130
131 /**
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700132 * The view shown if the detail list is empty.
133 * We set this to the list view when first bind the adapter, so that it won't be shown while
134 * we're loading data.
135 */
136 private View mEmptyView;
137
138 /**
139 * A list of distinct contact IDs included in the current contact.
140 */
141 private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
142 private ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
143 private ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
144 private ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
145 private ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
146 private ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
147 private ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
148 private ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
149 private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
150 private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
151 private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700152 private LayoutInflater mInflater;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700153
Daniel Lehmann25a02822010-05-11 15:52:43 -0700154 public ContactDetailFragment() {
155 // Explicit constructor for inflation
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700156
157 // Build the list of sections. The order they're added to mSections dictates the
158 // order they are displayed in the list.
159 mSections.add(mPhoneEntries);
160 mSections.add(mSmsEntries);
161 mSections.add(mEmailEntries);
162 mSections.add(mImEntries);
163 mSections.add(mPostalEntries);
164 mSections.add(mNicknameEntries);
165 mSections.add(mOrganizationEntries);
166 mSections.add(mGroupEntries);
167 mSections.add(mOtherEntries);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700168 }
169
170 @Override
171 public void onAttach(Activity activity) {
172 super.onAttach(activity);
173 mContext = activity;
174 }
175
176 @Override
Dianne Hackborn84df2b62010-05-12 19:04:42 -0700177 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700178 final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700179
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700180 setHasOptionsMenu(true);
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700181
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700182 mInflater = inflater;
183
184 mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700185 mHeaderView.setExcludeMimes(new String[] {
Daniel Lehmann25a02822010-05-11 15:52:43 -0700186 Contacts.CONTENT_ITEM_TYPE
187 });
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700188 mHeaderView.setListener(mHeaderViewListener);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700189
190 mListView = (ListView) view.findViewById(android.R.id.list);
191 mListView.setOnCreateContextMenuListener(this);
192 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
193 mListView.setOnItemClickListener(this);
194 // Don't set it to mListView yet. We do so later when we bind the adapter.
195 mEmptyView = view.findViewById(android.R.id.empty);
196
Daniel Lehmann25a02822010-05-11 15:52:43 -0700197 return view;
198 }
199
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700200 public void setListener(Listener value) {
201 mListener = value;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700202 }
203
Dmitri Plotnikov9cd17082010-07-09 14:10:04 -0700204 public Uri getUri() {
205 return mLookupUri;
206 }
207
Daniel Lehmann222744e2010-07-12 16:28:06 -0700208 @Override
209 public void onActivityCreated(Bundle savedInstanceState) {
210 super.onActivityCreated(savedInstanceState);
211
212 getLoaderManager().initLoader(LOADER_DETAILS, null, mDetailLoaderListener);
213 }
214
Daniel Lehmann25a02822010-05-11 15:52:43 -0700215 public void loadUri(Uri lookupUri) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700216 mLookupUri = lookupUri;
Daniel Lehmann222744e2010-07-12 16:28:06 -0700217 if (getActivity() != null) {
218 getLoaderManager().restartLoader(LOADER_DETAILS, null, mDetailLoaderListener);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700219 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700220 }
221
222 private void bindData() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700223 // Set the header
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700224 mHeaderView.loadData(mContactData);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700225
226 // Build up the contact entries
227 buildEntries();
228
229 // Collapse similar data items in select sections.
230 Collapser.collapseList(mPhoneEntries);
231 Collapser.collapseList(mSmsEntries);
232 Collapser.collapseList(mEmailEntries);
233 Collapser.collapseList(mPostalEntries);
234 Collapser.collapseList(mImEntries);
235
236 if (mAdapter == null) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700237 mAdapter = new ViewAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700238 mListView.setAdapter(mAdapter);
239 } else {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700240 mAdapter.notifyDataSetChanged();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700241 }
242 mListView.setEmptyView(mEmptyView);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700243
244 getActivity().invalidateOptionsMenu();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700245 }
246
247 /**
248 * Build up the entries to display on the screen.
249 */
250 private final void buildEntries() {
Daniel Lehmann7779f492010-08-24 15:26:36 -0700251 mHasPhone = PhoneCapabilityTester.isPhone(mContext);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700252 mHasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
253
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700254 // Clear out the old entries
255 final int numSections = mSections.size();
256 for (int i = 0; i < numSections; i++) {
257 mSections.get(i).clear();
258 }
259
260 mRawContactIds.clear();
261
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700262 mAllRestricted = true;
263 mPrimaryPhoneUri = null;
264 mNumPhoneNumbers = 0;
265
266 mWritableRawContactIds.clear();
267
268 final Sources sources = Sources.getInstance(mContext);
269
270 // Build up method entries
271 if (mContactData == null) {
272 return;
273 }
274
275 for (Entity entity: mContactData.getEntities()) {
276 final ContentValues entValues = entity.getEntityValues();
277 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
278 final long rawContactId = entValues.getAsLong(RawContacts._ID);
279
280 // Mark when this contact has any unrestricted components
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700281 Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
282 final boolean isRestricted = restricted != null && restricted != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700283 if (!isRestricted) mAllRestricted = false;
284
285 if (!mRawContactIds.contains(rawContactId)) {
286 mRawContactIds.add(rawContactId);
287 }
288 ContactsSource contactsSource = sources.getInflatedSource(accountType,
289 ContactsSource.LEVEL_SUMMARY);
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700290 if (contactsSource == null || !contactsSource.readOnly) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700291 mWritableRawContactIds.add(rawContactId);
292 }
293
294
295 for (NamedContentValues subValue : entity.getSubValues()) {
296 final ContentValues entryValues = subValue.values;
297 entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
298
299 final long dataId = entryValues.getAsLong(Data._ID);
300 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
301 if (mimeType == null) continue;
302
303 final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
304 ContactsSource.LEVEL_MIMETYPES);
305 if (kind == null) continue;
306
307 final ViewEntry entry = ViewEntry.fromValues(mContext, mimeType, kind,
308 rawContactId, dataId, entryValues);
309
310 final boolean hasData = !TextUtils.isEmpty(entry.data);
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700311 Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
312 final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700313
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700314 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
315 // Always ignore the name. It is shown in the header if set
316 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700317 // Build phone entries
318 mNumPhoneNumbers++;
319
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700320 final Intent phoneIntent = mHasPhone ? new Intent(Intent.ACTION_CALL_PRIVILEGED,
321 Uri.fromParts(Constants.SCHEME_TEL, entry.data, null)) : null;
322 final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
323 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
324
325 // Configure Icons and Intents. Notice actionIcon is already set to the phone
326 if (mHasPhone && mHasSms) {
327 entry.intent = phoneIntent;
328 entry.secondaryIntent = smsIntent;
329 entry.secondaryActionIcon = kind.iconAltRes;
330 } else if (mHasPhone) {
331 entry.intent = phoneIntent;
332 } else if (mHasSms) {
333 entry.intent = smsIntent;
334 entry.actionIcon = kind.iconAltRes;
335 } else {
336 entry.intent = null;
337 entry.actionIcon = -1;
338 }
339
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700340
341 // Remember super-primary phone
342 if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
343
344 entry.isPrimary = isSuperPrimary;
345 mPhoneEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700346 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
347 // Build email entries
348 entry.intent = new Intent(Intent.ACTION_SENDTO,
349 Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
350 entry.isPrimary = isSuperPrimary;
351 mEmailEntries.add(entry);
352
353 // When Email rows have status, create additional Im row
354 final DataStatus status = mContactData.getStatuses().get(entry.id);
355 if (status != null) {
356 final String imMime = Im.CONTENT_ITEM_TYPE;
357 final DataKind imKind = sources.getKindOrFallback(accountType,
358 imMime, mContext, ContactsSource.LEVEL_MIMETYPES);
359 final ViewEntry imEntry = ViewEntry.fromValues(mContext,
360 imMime, imKind, rawContactId, dataId, entryValues);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700361 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
362 if (imActions != null) {
363 imEntry.actionIcon = imActions.getPrimaryActionIcon();
364 imEntry.secondaryActionIcon = imActions.getSecondaryActionIcon();
365 imEntry.intent = imActions.getPrimaryIntent();
366 imEntry.secondaryIntent = imActions.getSecondaryIntent();
367 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700368 imEntry.applyStatus(status, false);
369 mImEntries.add(imEntry);
370 }
371 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
372 // Build postal entries
373 entry.maxLines = 4;
374 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
375 mPostalEntries.add(entry);
376 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
377 // Build IM entries
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700378 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
379 if (imActions != null) {
380 entry.actionIcon = imActions.getPrimaryActionIcon();
381 entry.secondaryActionIcon = imActions.getSecondaryActionIcon();
382 entry.intent = imActions.getPrimaryIntent();
383 entry.secondaryIntent = imActions.getSecondaryIntent();
384 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700385 if (TextUtils.isEmpty(entry.label)) {
386 entry.label = mContext.getString(R.string.chat).toLowerCase();
387 }
388
389 // Apply presence and status details when available
390 final DataStatus status = mContactData.getStatuses().get(entry.id);
391 if (status != null) {
392 entry.applyStatus(status, false);
393 }
394 mImEntries.add(entry);
395 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
396 (hasData || !TextUtils.isEmpty(entry.label))) {
397 // Build organization entries
398 final boolean isNameRawContact =
399 (mContactData.getNameRawContactId() == rawContactId);
400
401 final boolean duplicatesTitle =
402 isNameRawContact
403 && mContactData.getDisplayNameSource()
404 == DisplayNameSources.ORGANIZATION
405 && (!hasData || TextUtils.isEmpty(entry.label));
406
407 if (!duplicatesTitle) {
408 entry.uri = null;
409
410 if (TextUtils.isEmpty(entry.label)) {
411 entry.label = entry.data;
412 entry.data = "";
413 }
414
415 mOrganizationEntries.add(entry);
416 }
417 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
418 // Build nickname entries
419 final boolean isNameRawContact =
420 (mContactData.getNameRawContactId() == rawContactId);
421
422 final boolean duplicatesTitle =
423 isNameRawContact
424 && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
425
426 if (!duplicatesTitle) {
427 entry.uri = null;
428 mNicknameEntries.add(entry);
429 }
430 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
431 // Build note entries
432 entry.uri = null;
433 entry.maxLines = 100;
434 mOtherEntries.add(entry);
435 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
David Brown4423ca52010-08-30 18:56:37 -0700436 // Build Website entries
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700437 entry.uri = null;
438 entry.maxLines = 10;
439 try {
440 WebAddress webAddress = new WebAddress(entry.data);
441 entry.intent = new Intent(Intent.ACTION_VIEW,
442 Uri.parse(webAddress.toString()));
443 } catch (ParseException e) {
444 Log.e(TAG, "Couldn't parse website: " + entry.data);
445 }
446 mOtherEntries.add(entry);
David Brown4423ca52010-08-30 18:56:37 -0700447 } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
448 // Build SipAddress entries
449 entry.uri = null;
450 entry.maxLines = 1;
451 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
452 Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
453 mOtherEntries.add(entry);
454 // TODO: Consider moving the SipAddress into its own
455 // section (rather than lumping it in with mOtherEntries)
456 // so that we can reposition it right under the phone number.
457 // (Then, we'd also update FallbackSource.java to set
458 // secondary=false for this field, and tweak the weight
459 // of its DataKind.)
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700460 } else {
461 // Handle showing custom rows
462 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
463
464 // Use social summary when requested by external source
465 final DataStatus status = mContactData.getStatuses().get(entry.id);
466 final boolean hasSocial = kind.actionBodySocial && status != null;
467 if (hasSocial) {
468 entry.applyStatus(status, true);
469 }
470
471 if (hasSocial || hasData) {
472 mOtherEntries.add(entry);
473 }
474 }
475 }
476 }
477 }
478
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700479 private static String buildActionString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700480 boolean lowerCase, Context context) {
481 if (kind.actionHeader == null) {
482 return null;
483 }
484 CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
485 if (actionHeader == null) {
486 return null;
487 }
488 return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
489 }
490
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700491 private static String buildDataString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700492 Context context) {
493 if (kind.actionBody == null) {
494 return null;
495 }
496 CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
497 return actionBody == null ? null : actionBody.toString();
498 }
499
500 /**
501 * A basic structure with the data for a contact entry in the list.
502 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700503 private static class ViewEntry implements Collapsible<ViewEntry> {
504 // Copied from baseclass
505 public int type = -1;
506 public String label;
507 public String data;
508 public Uri uri;
509 public long id = 0;
510 public int maxLines = 1;
511 public String mimetype;
512
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700513 public Context context = null;
514 public String resPackageName = null;
515 public int actionIcon = -1;
516 public boolean isPrimary = false;
517 public int secondaryActionIcon = -1;
518 public Intent intent;
519 public Intent secondaryIntent = null;
520 public int maxLabelLines = 1;
521 public ArrayList<Long> ids = new ArrayList<Long>();
522 public int collapseCount = 0;
523
524 public int presence = -1;
525
526 public CharSequence footerLine = null;
527
528 private ViewEntry() {
529 }
530
531 /**
532 * Build new {@link ViewEntry} and populate from the given values.
533 */
534 public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
535 long rawContactId, long dataId, ContentValues values) {
536 final ViewEntry entry = new ViewEntry();
537 entry.context = context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700538 entry.id = dataId;
539 entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
540 entry.mimetype = mimeType;
541 entry.label = buildActionString(kind, values, false, context);
542 entry.data = buildDataString(kind, values, context);
543
544 if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
545 entry.type = values.getAsInteger(kind.typeColumn);
546 }
547 if (kind.iconRes > 0) {
548 entry.resPackageName = kind.resPackageName;
549 entry.actionIcon = kind.iconRes;
550 }
551
552 return entry;
553 }
554
555 /**
556 * Apply given {@link DataStatus} values over this {@link ViewEntry}
557 *
558 * @param fillData When true, the given status replaces {@link #data}
559 * and {@link #footerLine}. Otherwise only {@link #presence}
560 * is updated.
561 */
562 public ViewEntry applyStatus(DataStatus status, boolean fillData) {
563 presence = status.getPresence();
564 if (fillData && status.isValid()) {
565 this.data = status.getStatus().toString();
566 this.footerLine = status.getTimestampLabel(context);
567 }
568
569 return this;
570 }
571
572 public boolean collapseWith(ViewEntry entry) {
573 // assert equal collapse keys
574 if (!shouldCollapseWith(entry)) {
575 return false;
576 }
577
578 // Choose the label associated with the highest type precedence.
579 if (TypePrecedence.getTypePrecedence(mimetype, type)
580 > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
581 type = entry.type;
582 label = entry.label;
583 }
584
585 // Choose the max of the maxLines and maxLabelLines values.
586 maxLines = Math.max(maxLines, entry.maxLines);
587 maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines);
588
589 // Choose the presence with the highest precedence.
590 if (StatusUpdates.getPresencePrecedence(presence)
591 < StatusUpdates.getPresencePrecedence(entry.presence)) {
592 presence = entry.presence;
593 }
594
595 // If any of the collapsed entries are primary make the whole thing primary.
596 isPrimary = entry.isPrimary ? true : isPrimary;
597
598 // uri, and contactdId, shouldn't make a difference. Just keep the original.
599
600 // Keep track of all the ids that have been collapsed with this one.
601 ids.add(entry.id);
602 collapseCount++;
603 return true;
604 }
605
606 public boolean shouldCollapseWith(ViewEntry entry) {
607 if (entry == null) {
608 return false;
609 }
610
611 if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
612 entry.data)) {
613 return false;
614 }
615
616 if (!TextUtils.equals(mimetype, entry.mimetype)
617 || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
618 || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
619 || actionIcon != entry.actionIcon) {
620 return false;
621 }
622
623 return true;
624 }
625 }
626
627 /** Cache of the children views of a row */
628 private static class ViewCache {
629 public TextView label;
630 public TextView data;
631 public TextView footer;
632 public ImageView actionIcon;
633 public ImageView presenceIcon;
634 public ImageView primaryIcon;
635 public ImageView secondaryActionButton;
636 public View secondaryActionDivider;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700637 }
638
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700639 private final class ViewAdapter extends BaseAdapter {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700640 public View getView(int position, View convertView, ViewGroup parent) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700641 final ViewEntry entry = getEntry(position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700642 final View v;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700643 final ViewCache viewCache;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700644
645 // Check to see if we can reuse convertView
646 if (convertView != null) {
647 v = convertView;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700648 viewCache = (ViewCache) v.getTag();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700649 } else {
650 // Create a new view if needed
651 v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
652
653 // Cache the children
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700654 viewCache = new ViewCache();
655 viewCache.label = (TextView) v.findViewById(android.R.id.text1);
656 viewCache.data = (TextView) v.findViewById(android.R.id.text2);
657 viewCache.footer = (TextView) v.findViewById(R.id.footer);
658 viewCache.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
659 viewCache.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
660 viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
661 viewCache.secondaryActionButton = (ImageView) v.findViewById(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700662 R.id.secondary_action_button);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700663 viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700664 viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
665 v.setTag(viewCache);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700666 }
667
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700668 // Bind the data to the view
669 bindView(v, entry);
670 return v;
671 }
672
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700673 protected void bindView(View view, ViewEntry entry) {
674 final Resources resources = mContext.getResources();
675 ViewCache views = (ViewCache) view.getTag();
676
677 // Set the label
678 TextView label = views.label;
679 setMaxLines(label, entry.maxLabelLines);
680 label.setText(entry.label);
681
682 // Set the data
683 TextView data = views.data;
684 if (data != null) {
685 if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE)
686 || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
687 data.setText(PhoneNumberUtils.formatNumber(entry.data));
688 } else {
689 data.setText(entry.data);
690 }
691 setMaxLines(data, entry.maxLines);
692 }
693
694 // Set the footer
695 if (!TextUtils.isEmpty(entry.footerLine)) {
696 views.footer.setText(entry.footerLine);
697 views.footer.setVisibility(View.VISIBLE);
698 } else {
699 views.footer.setVisibility(View.GONE);
700 }
701
702 // Set the primary icon
703 views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
704
705 // Set the action icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700706 final ImageView action = views.actionIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700707 if (entry.actionIcon != -1) {
708 Drawable actionIcon;
709 if (entry.resPackageName != null) {
710 // Load external resources through PackageManager
711 actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
712 entry.actionIcon, null);
713 } else {
714 actionIcon = resources.getDrawable(entry.actionIcon);
715 }
716 action.setImageDrawable(actionIcon);
717 action.setVisibility(View.VISIBLE);
718 } else {
719 // Things should still line up as if there was an icon, so make it invisible
720 action.setVisibility(View.INVISIBLE);
721 }
722
723 // Set the presence icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700724 final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700725 mContext, entry.presence);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700726 final ImageView presenceIconView = views.presenceIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700727 if (presenceIcon != null) {
728 presenceIconView.setImageDrawable(presenceIcon);
729 presenceIconView.setVisibility(View.VISIBLE);
730 } else {
731 presenceIconView.setVisibility(View.GONE);
732 }
733
734 // Set the secondary action button
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700735 final ImageView secondaryActionView = views.secondaryActionButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700736 Drawable secondaryActionIcon = null;
737 if (entry.secondaryActionIcon != -1) {
738 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
739 }
740 if (entry.secondaryIntent != null && secondaryActionIcon != null) {
741 secondaryActionView.setImageDrawable(secondaryActionIcon);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700742 secondaryActionView.setTag(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700743 secondaryActionView.setVisibility(View.VISIBLE);
744 views.secondaryActionDivider.setVisibility(View.VISIBLE);
745 } else {
746 secondaryActionView.setVisibility(View.GONE);
747 views.secondaryActionDivider.setVisibility(View.GONE);
748 }
749 }
750
751 private void setMaxLines(TextView textView, int maxLines) {
752 if (maxLines == 1) {
753 textView.setSingleLine(true);
754 textView.setEllipsize(TextUtils.TruncateAt.END);
755 } else {
756 textView.setSingleLine(false);
757 textView.setMaxLines(maxLines);
758 textView.setEllipsize(null);
759 }
760 }
761
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700762 private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
763 public void onClick(View v) {
764 if (mListener == null) return;
765 if (v == null) return;
766 final ViewEntry entry = (ViewEntry) v.getTag();
767 if (entry == null) return;
768 final Intent intent = entry.secondaryIntent;
769 if (intent == null) return;
770 mListener.onItemClicked(intent);
771 }
772 };
773
774 public int getCount() {
775 int count = 0;
776 final int numSections = mSections.size();
777 for (int i = 0; i < numSections; i++) {
778 final ArrayList<ViewEntry> section = mSections.get(i);
779 count += section.size();
780 }
781 return count;
782 }
783
784 public Object getItem(int position) {
785 return getEntry(position);
786 }
787
788 public long getItemId(int position) {
789 final ViewEntry entry = getEntry(position);
790 if (entry != null) {
791 return entry.id;
792 } else {
793 return -1;
794 }
795 }
796
797 private ViewEntry getEntry(int position) {
798 final int numSections = mSections.size();
799 for (int i = 0; i < numSections; i++) {
800 final ArrayList<ViewEntry> section = mSections.get(i);
801 final int sectionSize = section.size();
802 if (position < sectionSize) {
803 return section.get(position);
804 }
805 position -= sectionSize;
806 }
807 return null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700808 }
809 }
810
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200811 @Override
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700812 public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700813 inflater.inflate(R.menu.view, menu);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700814 }
815
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200816 @Override
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700817 public void onPrepareOptionsMenu(Menu menu) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700818 if (mContactData == null) {
819 return;
820 }
821
822 boolean isDirectoryEntry = mContactData.isDirectoryEntry();
823
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700824 // Options only shows telephony-related settings (ringtone, send to voicemail).
825 // ==> Hide if we don't have a telephone
826 final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
Daniel Lehmann7779f492010-08-24 15:26:36 -0700827 final boolean deviceHasPhone = PhoneCapabilityTester.isPhone(mContext);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700828 optionsMenu.setVisible(!isDirectoryEntry && deviceHasPhone);
829
830 final MenuItem editMenu = menu.findItem(R.id.menu_edit);
831 editMenu.setVisible(!isDirectoryEntry);
832
833 final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
834 deleteMenu.setVisible(!isDirectoryEntry);
835
836 final MenuItem shareMenu = menu.findItem(R.id.menu_share);
837 shareMenu.setVisible(!isDirectoryEntry && !mAllRestricted);
838
839 final MenuItem copyMenu = menu.findItem(R.id.menu_copy);
840 if (isDirectoryEntry) {
841 int exportSupport = mContactData.getDirectoryExportSupport();
842 copyMenu.setVisible(exportSupport == Directory.EXPORT_SUPPORT_ANY_ACCOUNT
843 || exportSupport == Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY);
844 } else {
845 copyMenu.setVisible(false);
846 }
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700847 }
848
849 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700850 public boolean onOptionsItemSelected(MenuItem item) {
851 switch (item.getItemId()) {
852 case R.id.menu_edit: {
Daniel Lehmann99eefbe2010-08-24 16:45:03 -0700853 if (mListener != null) mListener.onEditRequested(mLookupUri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700854 break;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700855 }
856 case R.id.menu_delete: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700857 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700858 return true;
859 }
860 case R.id.menu_options: {
861 final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
862 intent.setData(mContactData.getLookupUri());
863 mContext.startActivity(intent);
864 return true;
865 }
866 case R.id.menu_share: {
867 if (mAllRestricted) return false;
868
869 final String lookupKey = mContactData.getLookupKey();
870 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
871
872 final Intent intent = new Intent(Intent.ACTION_SEND);
873 intent.setType(Contacts.CONTENT_VCARD_TYPE);
874 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
875
876 // Launch chooser to share contact via
877 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
878 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
879
880 try {
881 mContext.startActivity(chooseIntent);
882 } catch (ActivityNotFoundException ex) {
883 Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
884 }
885 return true;
886 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700887 case R.id.menu_copy: {
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700888 makePersonalCopy();
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700889 return true;
890 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700891 }
892 return false;
893 }
894
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700895 private void makePersonalCopy() {
896 if (mListener == null) {
897 return;
898 }
899
900 int exportSupport = mContactData.getDirectoryExportSupport();
901 switch (exportSupport) {
902 case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY: {
903 createCopy(new Account(mContactData.getDirectoryAccountName(),
904 mContactData.getDirectoryAccountType()));
905 break;
906 }
907 case Directory.EXPORT_SUPPORT_ANY_ACCOUNT: {
908 final ArrayList<Account> accounts = Sources.getInstance(mContext).getAccounts(true);
909 if (accounts.isEmpty()) {
910 createCopy(null);
911 return; // Don't show a dialog.
912 }
913
914 // In the common case of a single writable account, auto-select
915 // it without showing a dialog.
916 if (accounts.size() == 1) {
917 createCopy(accounts.get(0));
918 return; // Don't show a dialog.
919 }
920
921 final SelectAccountDialogFragment dialog =
922 new SelectAccountDialogFragment(getId(), true);
923 dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
924 break;
925 }
926 }
927 }
928
929 @Override
930 public void onAccountSelectorCancelled() {
931 }
932
933 @Override
934 public void onAccountChosen(Account account, boolean isNewContact) {
935 createCopy(account);
936 }
937
938 private void createCopy(Account account) {
939 if (mListener != null) {
940 mListener.onCreateRawContactRequested(mContactData.getContentValues(), account);
941 }
942 }
943
Dmitri Plotnikovd3643142010-07-09 13:49:07 -0700944 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700945 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
946 AdapterView.AdapterContextMenuInfo info;
947 try {
948 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
949 } catch (ClassCastException e) {
950 Log.e(TAG, "bad menuInfo", e);
951 return;
952 }
953
954 // This can be null sometimes, don't crash...
955 if (info == null) {
956 Log.e(TAG, "bad menuInfo");
957 return;
958 }
959
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700960 final ViewEntry entry = mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700961 menu.setHeaderTitle(R.string.contactOptionsTitle);
962 if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700963 if (mHasPhone) {
964 menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
965 }
966 if (mHasSms) {
967 // If there is no Phone, SMS is the primary intent
968 final Intent intent = mHasPhone ? entry.secondaryIntent : entry.intent;
969 menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(intent);
970 }
971 if (!entry.isPrimary && mHasPhone) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700972 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
973 }
974 } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
975 menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
976 if (!entry.isPrimary) {
977 menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
978 }
979 } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
980 menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
981 }
982 }
983
984 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700985 if (mListener == null) return;
986 final ViewEntry entry = mAdapter.getEntry(position);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700987 if (entry == null) return;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700988 final Intent intent = entry.intent;
989 if (intent == null) return;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700990 mListener.onItemClicked(intent);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700991 }
992
Dmitri Plotnikovd3643142010-07-09 13:49:07 -0700993 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700994 public boolean onContextItemSelected(MenuItem item) {
995 switch (item.getItemId()) {
996 case MENU_ITEM_MAKE_DEFAULT: {
997 if (makeItemDefault(item)) {
998 return true;
999 }
1000 break;
1001 }
1002 }
1003
1004 return false;
1005 }
1006
1007 private boolean makeItemDefault(MenuItem item) {
1008 ViewEntry entry = getViewEntryForMenuItem(item);
1009 if (entry == null) {
1010 return false;
1011 }
1012
1013 // Update the primary values in the data record.
1014 ContentValues values = new ContentValues(1);
1015 values.put(Data.IS_SUPER_PRIMARY, 1);
1016
1017 mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
1018 values, null, null);
1019 return true;
1020 }
1021
1022 private ViewEntry getViewEntryForMenuItem(MenuItem item) {
1023 AdapterView.AdapterContextMenuInfo info;
1024 try {
1025 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
1026 } catch (ClassCastException e) {
1027 Log.e(TAG, "bad menuInfo", e);
1028 return null;
1029 }
1030
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001031 return mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001032 }
1033
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001034 public boolean onKeyDown(int keyCode, KeyEvent event) {
1035 switch (keyCode) {
1036 case KeyEvent.KEYCODE_CALL: {
1037 try {
1038 ITelephony phone = ITelephony.Stub.asInterface(
1039 ServiceManager.checkService("phone"));
1040 if (phone != null && !phone.isIdle()) {
1041 // Skip out and let the key be handled at a higher level
1042 break;
1043 }
1044 } catch (RemoteException re) {
1045 // Fall through and try to call the contact
1046 }
1047
1048 int index = mListView.getSelectedItemPosition();
1049 if (index != -1) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001050 final ViewEntry entry = mAdapter.getEntry(index);
Daniel Lehmann0deb6d32010-08-05 11:37:36 -07001051 if (entry != null && entry.intent != null &&
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001052 entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
1053 mContext.startActivity(entry.intent);
1054 return true;
1055 }
1056 } else if (mPrimaryPhoneUri != null) {
1057 // There isn't anything selected, call the default number
1058 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1059 mPrimaryPhoneUri);
1060 mContext.startActivity(intent);
1061 return true;
1062 }
1063 return false;
1064 }
1065
1066 case KeyEvent.KEYCODE_DEL: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001067 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001068 return true;
1069 }
1070 }
1071
Daniel Lehmannc2687c32010-04-19 18:20:44 -07001072 return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001073 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001074
Daniel Lehmann222744e2010-07-12 16:28:06 -07001075 /**
1076 * The listener for the detail loader
1077 */
1078 private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
1079 new LoaderCallbacks<ContactLoader.Result>() {
1080 @Override
1081 public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
1082 return new ContactLoader(mContext, mLookupUri);
1083 }
1084
1085 @Override
1086 public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
1087 if (data == ContactLoader.Result.NOT_FOUND) {
1088 // Item has been deleted
1089 Log.i(TAG, "No contact found. Closing activity");
Daniel Lehmann99eefbe2010-08-24 16:45:03 -07001090 if (mListener != null) mListener.onContactNotFound();
Daniel Lehmann222744e2010-07-12 16:28:06 -07001091 return;
1092 }
1093 mContactData = data;
1094 bindData();
1095 }
1096 };
1097
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001098 private ContactDetailHeaderView.Listener mHeaderViewListener =
1099 new ContactDetailHeaderView.Listener() {
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001100 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001101 public void onDisplayNameClick(View view) {
1102 }
1103
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001104 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001105 public void onPhotoClick(View view) {
1106 }
1107 };
1108
1109
1110 public static interface Listener {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001111 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001112 * Contact was not found, so somehow close this fragment. This is raised after a contact
1113 * is removed via Menu/Delete
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001114 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001115 public void onContactNotFound();
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001116
1117 /**
1118 * User decided to go to Edit-Mode
1119 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001120 public void onEditRequested(Uri lookupUri);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001121
1122 /**
1123 * User clicked a single item (e.g. mail)
1124 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001125 public void onItemClicked(Intent intent);
1126
1127 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001128 * User decided to delete the contact
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001129 */
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001130 public void onDeleteRequested(Uri lookupUri);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001131
1132 /**
1133 * User requested creation of a new contact with the specified values.
1134 *
1135 * @param values ContentValues containing data rows for the new contact.
1136 * @param account Account where the new contact should be created
1137 */
1138 public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001139 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001140}