blob: 1292ad5b4e25c0648c1907d628c83e723eba9894 [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;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -070029import com.android.contacts.model.ContactsSource.EditType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070030import com.android.contacts.model.Sources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070031import com.android.contacts.util.Constants;
32import com.android.contacts.util.DataStatus;
Dmitri Plotnikov45b44022010-10-15 18:12:09 -070033import com.android.contacts.util.DateUtils;
Daniel Lehmannf77b8e92010-07-16 18:58:36 -070034import com.android.contacts.util.PhoneCapabilityTester;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070035import com.android.contacts.views.ContactLoader;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070036import com.android.contacts.views.GroupMetaData;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070037import com.android.contacts.views.editor.SelectAccountDialogFragment;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070038import com.android.internal.telephony.ITelephony;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070039
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070040import android.accounts.Account;
Daniel Lehmann25a02822010-05-11 15:52:43 -070041import android.app.Activity;
Daniel Lehmann222744e2010-07-12 16:28:06 -070042import android.app.Fragment;
43import android.app.LoaderManager;
44import android.app.LoaderManager.LoaderCallbacks;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070045import android.content.ActivityNotFoundException;
46import android.content.ContentUris;
47import android.content.ContentValues;
48import android.content.Context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070049import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070050import android.content.Entity.NamedContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070051import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050052import android.content.Loader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053import android.content.res.Resources;
54import android.graphics.drawable.Drawable;
55import android.net.ParseException;
56import android.net.Uri;
57import android.net.WebAddress;
58import android.os.Bundle;
59import android.os.RemoteException;
60import android.os.ServiceManager;
61import android.provider.ContactsContract.CommonDataKinds;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070062import android.provider.ContactsContract.CommonDataKinds.Email;
Dmitri Plotnikov45b44022010-10-15 18:12:09 -070063import android.provider.ContactsContract.CommonDataKinds.Event;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070064import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070065import android.provider.ContactsContract.CommonDataKinds.Im;
66import android.provider.ContactsContract.CommonDataKinds.Nickname;
67import android.provider.ContactsContract.CommonDataKinds.Note;
68import android.provider.ContactsContract.CommonDataKinds.Organization;
69import android.provider.ContactsContract.CommonDataKinds.Phone;
David Brown4423ca52010-08-30 18:56:37 -070070import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070071import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070072import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
73import android.provider.ContactsContract.CommonDataKinds.Website;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070074import android.provider.ContactsContract.Contacts;
75import android.provider.ContactsContract.Data;
76import android.provider.ContactsContract.Directory;
77import android.provider.ContactsContract.DisplayNameSources;
Bai Tao09eb04f2010-09-01 15:34:16 +080078import android.provider.ContactsContract.PhoneLookup;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070079import android.provider.ContactsContract.RawContacts;
80import android.provider.ContactsContract.StatusUpdates;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070081import android.telephony.PhoneNumberUtils;
82import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070083import android.util.Log;
84import android.view.ContextMenu;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070085import android.view.ContextMenu.ContextMenuInfo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070086import android.view.KeyEvent;
87import android.view.LayoutInflater;
88import android.view.Menu;
89import android.view.MenuInflater;
90import android.view.MenuItem;
91import android.view.View;
Daniel Lehmannc2687c32010-04-19 18:20:44 -070092import android.view.View.OnClickListener;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070093import android.view.View.OnCreateContextMenuListener;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070094import android.view.ViewGroup;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070095import android.widget.AdapterView;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070096import android.widget.AdapterView.OnItemClickListener;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070097import android.widget.BaseAdapter;
Daniel Lehmann18db43d2010-09-30 18:16:59 -070098import android.widget.Button;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070099import android.widget.ImageView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700100import android.widget.ListView;
101import android.widget.TextView;
102import android.widget.Toast;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700103
104import java.util.ArrayList;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700105import java.util.Collections;
106import java.util.List;
Dmitri Plotnikov45b44022010-10-15 18:12:09 -0700107import java.util.Locale;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700108
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700109public class ContactDetailFragment extends Fragment implements OnCreateContextMenuListener,
110 OnItemClickListener, SelectAccountDialogFragment.Listener {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700111 private static final String TAG = "ContactDetailFragment";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700112
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700113 private static final int LOADER_DETAILS = 1;
114
Daniel Lehmann25a02822010-05-11 15:52:43 -0700115 private Context mContext;
116 private Uri mLookupUri;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700117 private Listener mListener;
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700118
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700119 private ContactLoader.Result mContactData;
120 private ContactDetailHeaderView mHeaderView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700121 private ListView mListView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700122 private ViewAdapter mAdapter;
123 private Uri mPrimaryPhoneUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700124
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700125 private Button mCopyGalToLocalButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700126 private boolean mAllRestricted;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700127 private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700128 private int mNumPhoneNumbers = 0;
Bai Tao09eb04f2010-09-01 15:34:16 +0800129 private String mDefaultCountryIso;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700130
131 /**
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700132 * Device capability: Set during buildEntries and used in the long-press context menu
133 */
134 private boolean mHasPhone;
135
136 /**
137 * Device capability: Set during buildEntries and used in the long-press context menu
138 */
139 private boolean mHasSms;
140
141 /**
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700142 * The view shown if the detail list is empty.
143 * We set this to the list view when first bind the adapter, so that it won't be shown while
144 * we're loading data.
145 */
146 private View mEmptyView;
147
148 /**
149 * A list of distinct contact IDs included in the current contact.
150 */
151 private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
152 private ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
153 private ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
154 private ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
155 private ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
156 private ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
157 private ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
158 private ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
159 private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
160 private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
161 private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700162 private LayoutInflater mInflater;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700163
Daniel Lehmann25a02822010-05-11 15:52:43 -0700164 public ContactDetailFragment() {
165 // Explicit constructor for inflation
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700166
167 // Build the list of sections. The order they're added to mSections dictates the
168 // order they are displayed in the list.
169 mSections.add(mPhoneEntries);
170 mSections.add(mSmsEntries);
171 mSections.add(mEmailEntries);
172 mSections.add(mImEntries);
173 mSections.add(mPostalEntries);
174 mSections.add(mNicknameEntries);
175 mSections.add(mOrganizationEntries);
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700176 mSections.add(mOtherEntries);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700177 mSections.add(mGroupEntries);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700178 }
179
180 @Override
181 public void onAttach(Activity activity) {
182 super.onAttach(activity);
183 mContext = activity;
Bai Tao09eb04f2010-09-01 15:34:16 +0800184 mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(mContext);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700185 }
186
187 @Override
Dianne Hackborn84df2b62010-05-12 19:04:42 -0700188 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700189 final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700190
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700191 setHasOptionsMenu(true);
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700192
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700193 mInflater = inflater;
194
195 mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700196 mHeaderView.setListener(mHeaderViewListener);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700197
198 mListView = (ListView) view.findViewById(android.R.id.list);
199 mListView.setOnCreateContextMenuListener(this);
200 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
201 mListView.setOnItemClickListener(this);
202 // Don't set it to mListView yet. We do so later when we bind the adapter.
203 mEmptyView = view.findViewById(android.R.id.empty);
204
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700205 mCopyGalToLocalButton = (Button) view.findViewById(R.id.copyLocal);
206 mCopyGalToLocalButton.setOnClickListener(new OnClickListener() {
207 @Override
208 public void onClick(View v) {
209 makePersonalCopy();
210 }
211 });
212
Daniel Lehmann25a02822010-05-11 15:52:43 -0700213 return view;
214 }
215
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700216 public void setListener(Listener value) {
217 mListener = value;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700218 }
219
Dmitri Plotnikov9cd17082010-07-09 14:10:04 -0700220 public Uri getUri() {
221 return mLookupUri;
222 }
223
Daniel Lehmann222744e2010-07-12 16:28:06 -0700224 @Override
225 public void onActivityCreated(Bundle savedInstanceState) {
226 super.onActivityCreated(savedInstanceState);
227
228 getLoaderManager().initLoader(LOADER_DETAILS, null, mDetailLoaderListener);
229 }
230
Daniel Lehmann25a02822010-05-11 15:52:43 -0700231 public void loadUri(Uri lookupUri) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700232 mLookupUri = lookupUri;
Daniel Lehmann222744e2010-07-12 16:28:06 -0700233 if (getActivity() != null) {
234 getLoaderManager().restartLoader(LOADER_DETAILS, null, mDetailLoaderListener);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700235 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700236 }
237
238 private void bindData() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700239 // Set the header
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700240 mHeaderView.loadData(mContactData);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700241
242 // Build up the contact entries
243 buildEntries();
244
245 // Collapse similar data items in select sections.
246 Collapser.collapseList(mPhoneEntries);
247 Collapser.collapseList(mSmsEntries);
248 Collapser.collapseList(mEmailEntries);
249 Collapser.collapseList(mPostalEntries);
250 Collapser.collapseList(mImEntries);
251
252 if (mAdapter == null) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700253 mAdapter = new ViewAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700254 mListView.setAdapter(mAdapter);
255 } else {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700256 mAdapter.notifyDataSetChanged();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700257 }
258 mListView.setEmptyView(mEmptyView);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700259
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700260 // Configure copy gal button
261 if (mContactData.isDirectoryEntry()) {
262 final int exportSupport = mContactData.getDirectoryExportSupport();
263 if (exportSupport == Directory.EXPORT_SUPPORT_ANY_ACCOUNT
264 || exportSupport == Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
265 mCopyGalToLocalButton.setVisibility(View.VISIBLE);
266 } else {
267 mCopyGalToLocalButton.setVisibility(View.GONE);
268 }
269 } else {
270 mCopyGalToLocalButton.setVisibility(View.GONE);
271 }
272
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700273 getActivity().invalidateOptionsMenu();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700274 }
275
276 /**
277 * Build up the entries to display on the screen.
278 */
279 private final void buildEntries() {
Daniel Lehmann7779f492010-08-24 15:26:36 -0700280 mHasPhone = PhoneCapabilityTester.isPhone(mContext);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700281 mHasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
282
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700283 // Clear out the old entries
284 final int numSections = mSections.size();
285 for (int i = 0; i < numSections; i++) {
286 mSections.get(i).clear();
287 }
288
289 mRawContactIds.clear();
290
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700291 mAllRestricted = true;
292 mPrimaryPhoneUri = null;
293 mNumPhoneNumbers = 0;
294
295 mWritableRawContactIds.clear();
296
297 final Sources sources = Sources.getInstance(mContext);
298
299 // Build up method entries
300 if (mContactData == null) {
301 return;
302 }
303
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700304 ArrayList<String> groups = new ArrayList<String>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700305 for (Entity entity: mContactData.getEntities()) {
306 final ContentValues entValues = entity.getEntityValues();
307 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
308 final long rawContactId = entValues.getAsLong(RawContacts._ID);
309
310 // Mark when this contact has any unrestricted components
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700311 Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
312 final boolean isRestricted = restricted != null && restricted != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700313 if (!isRestricted) mAllRestricted = false;
314
315 if (!mRawContactIds.contains(rawContactId)) {
316 mRawContactIds.add(rawContactId);
317 }
318 ContactsSource contactsSource = sources.getInflatedSource(accountType,
319 ContactsSource.LEVEL_SUMMARY);
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700320 if (contactsSource == null || !contactsSource.readOnly) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700321 mWritableRawContactIds.add(rawContactId);
322 }
323
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700324 for (NamedContentValues subValue : entity.getSubValues()) {
325 final ContentValues entryValues = subValue.values;
326 entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
327
328 final long dataId = entryValues.getAsLong(Data._ID);
329 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
330 if (mimeType == null) continue;
331
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700332 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
333 Long groupId = entryValues.getAsLong(GroupMembership.GROUP_ROW_ID);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700334 if (groupId != null) {
335 handleGroupMembership(groups, mContactData.getGroupMetaData(), groupId);
336 }
337 continue;
338 }
339
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700340 final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700341 ContactsSource.LEVEL_CONSTRAINTS);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700342 if (kind == null) continue;
343
Daniel Lehmann16249642010-09-10 19:56:51 -0700344 final ViewEntry entry = ViewEntry.fromValues(mContext, mimeType, kind, dataId,
345 entryValues);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700346
347 final boolean hasData = !TextUtils.isEmpty(entry.data);
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700348 Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
349 final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700350
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700351 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
352 // Always ignore the name. It is shown in the header if set
353 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700354 // Build phone entries
355 mNumPhoneNumbers++;
Bai Tao09eb04f2010-09-01 15:34:16 +0800356 String phoneNumberE164 =
357 entryValues.getAsString(PhoneLookup.NORMALIZED_NUMBER);
358 entry.data = PhoneNumberUtils.formatNumber(
359 entry.data, phoneNumberE164, mDefaultCountryIso);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700360 final Intent phoneIntent = mHasPhone ? new Intent(Intent.ACTION_CALL_PRIVILEGED,
361 Uri.fromParts(Constants.SCHEME_TEL, entry.data, null)) : null;
362 final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
363 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
364
365 // Configure Icons and Intents. Notice actionIcon is already set to the phone
366 if (mHasPhone && mHasSms) {
367 entry.intent = phoneIntent;
368 entry.secondaryIntent = smsIntent;
369 entry.secondaryActionIcon = kind.iconAltRes;
370 } else if (mHasPhone) {
371 entry.intent = phoneIntent;
372 } else if (mHasSms) {
373 entry.intent = smsIntent;
374 entry.actionIcon = kind.iconAltRes;
375 } else {
376 entry.intent = null;
377 entry.actionIcon = -1;
378 }
379
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700380
381 // Remember super-primary phone
382 if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
383
384 entry.isPrimary = isSuperPrimary;
385 mPhoneEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700386 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
387 // Build email entries
388 entry.intent = new Intent(Intent.ACTION_SENDTO,
389 Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
390 entry.isPrimary = isSuperPrimary;
391 mEmailEntries.add(entry);
392
393 // When Email rows have status, create additional Im row
394 final DataStatus status = mContactData.getStatuses().get(entry.id);
395 if (status != null) {
396 final String imMime = Im.CONTENT_ITEM_TYPE;
397 final DataKind imKind = sources.getKindOrFallback(accountType,
398 imMime, mContext, ContactsSource.LEVEL_MIMETYPES);
399 final ViewEntry imEntry = ViewEntry.fromValues(mContext,
Daniel Lehmann16249642010-09-10 19:56:51 -0700400 imMime, imKind, dataId, entryValues);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700401 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
402 if (imActions != null) {
403 imEntry.actionIcon = imActions.getPrimaryActionIcon();
404 imEntry.secondaryActionIcon = imActions.getSecondaryActionIcon();
405 imEntry.intent = imActions.getPrimaryIntent();
406 imEntry.secondaryIntent = imActions.getSecondaryIntent();
407 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700408 imEntry.applyStatus(status, false);
409 mImEntries.add(imEntry);
410 }
411 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
412 // Build postal entries
413 entry.maxLines = 4;
414 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
415 mPostalEntries.add(entry);
416 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
417 // Build IM entries
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700418 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
419 if (imActions != null) {
420 entry.actionIcon = imActions.getPrimaryActionIcon();
421 entry.secondaryActionIcon = imActions.getSecondaryActionIcon();
422 entry.intent = imActions.getPrimaryIntent();
423 entry.secondaryIntent = imActions.getSecondaryIntent();
424 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700425 if (TextUtils.isEmpty(entry.kindAndType)) {
426 entry.kindAndType = mContext.getString(R.string.chat).toLowerCase();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700427 }
428
429 // Apply presence and status details when available
430 final DataStatus status = mContactData.getStatuses().get(entry.id);
431 if (status != null) {
432 entry.applyStatus(status, false);
433 }
434 mImEntries.add(entry);
435 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700436 (hasData || !TextUtils.isEmpty(entry.typeString))) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700437 // Build organization entries
438 final boolean isNameRawContact =
439 (mContactData.getNameRawContactId() == rawContactId);
440
441 final boolean duplicatesTitle =
442 isNameRawContact
443 && mContactData.getDisplayNameSource()
444 == DisplayNameSources.ORGANIZATION
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700445 && (!hasData || TextUtils.isEmpty(entry.typeString));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700446
447 if (!duplicatesTitle) {
448 entry.uri = null;
449
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700450 if (TextUtils.isEmpty(entry.typeString)) {
451 entry.kindAndType = entry.data;
452 entry.typeString = entry.data;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700453 entry.data = "";
454 }
455
456 mOrganizationEntries.add(entry);
457 }
458 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
459 // Build nickname entries
460 final boolean isNameRawContact =
461 (mContactData.getNameRawContactId() == rawContactId);
462
463 final boolean duplicatesTitle =
464 isNameRawContact
465 && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
466
467 if (!duplicatesTitle) {
468 entry.uri = null;
469 mNicknameEntries.add(entry);
470 }
471 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
472 // Build note entries
473 entry.uri = null;
474 entry.maxLines = 100;
475 mOtherEntries.add(entry);
476 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
David Brown4423ca52010-08-30 18:56:37 -0700477 // Build Website entries
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700478 entry.uri = null;
479 entry.maxLines = 10;
480 try {
481 WebAddress webAddress = new WebAddress(entry.data);
482 entry.intent = new Intent(Intent.ACTION_VIEW,
483 Uri.parse(webAddress.toString()));
484 } catch (ParseException e) {
485 Log.e(TAG, "Couldn't parse website: " + entry.data);
486 }
487 mOtherEntries.add(entry);
David Brown4423ca52010-08-30 18:56:37 -0700488 } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
489 // Build SipAddress entries
490 entry.uri = null;
491 entry.maxLines = 1;
492 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
493 Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
494 mOtherEntries.add(entry);
495 // TODO: Consider moving the SipAddress into its own
496 // section (rather than lumping it in with mOtherEntries)
497 // so that we can reposition it right under the phone number.
498 // (Then, we'd also update FallbackSource.java to set
499 // secondary=false for this field, and tweak the weight
500 // of its DataKind.)
Dmitri Plotnikov45b44022010-10-15 18:12:09 -0700501 } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
502 entry.data = DateUtils.formatDate(mContext, entry.data);
503 entry.uri = null;
504 mOtherEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700505 } else {
506 // Handle showing custom rows
507 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
508
509 // Use social summary when requested by external source
510 final DataStatus status = mContactData.getStatuses().get(entry.id);
511 final boolean hasSocial = kind.actionBodySocial && status != null;
512 if (hasSocial) {
513 entry.applyStatus(status, true);
514 }
515
516 if (hasSocial || hasData) {
517 mOtherEntries.add(entry);
518 }
519 }
520 }
521 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700522
523 if (!groups.isEmpty()) {
524 ViewEntry entry = new ViewEntry();
525 Collections.sort(groups);
526 StringBuilder sb = new StringBuilder();
527 int size = groups.size();
528 for (int i = 0; i < size; i++) {
529 if (i != 0) {
530 sb.append(", ");
531 }
532 sb.append(groups.get(i));
533 }
534 entry.mimetype = GroupMembership.MIMETYPE;
535 entry.kind = mContext.getString(R.string.groupsLabel);
536 entry.data = sb.toString();
537 mGroupEntries.add(entry);
538 }
539 }
540
541 /**
542 * Maps group ID to the corresponding group name, collapses all synonymous groups.
543 * Ignores default groups (e.g. My Contacts) and favorites groups.
544 */
545 private void handleGroupMembership(
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700546 ArrayList<String> groups, List<GroupMetaData> groupMetaData, long groupId) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700547 if (groupMetaData == null) {
548 return;
549 }
550
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700551 for (GroupMetaData group : groupMetaData) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700552 if (group.getGroupId() == groupId) {
553 if (!group.isDefaultGroup() && !group.isFavorites()) {
554 String title = group.getTitle();
555 if (!groups.contains(title)) {
556 groups.add(title);
557 }
558 }
559 break;
560 }
561 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700562 }
563
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700564 private static String buildActionString(DataKind kind, ContentValues values, Context context) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700565 if (kind.actionHeader == null) {
566 return null;
567 }
568 CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
569 if (actionHeader == null) {
570 return null;
571 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700572 return actionHeader.toString();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700573 }
574
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700575 private static String buildDataString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700576 Context context) {
577 if (kind.actionBody == null) {
578 return null;
579 }
580 CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
581 return actionBody == null ? null : actionBody.toString();
582 }
583
584 /**
585 * A basic structure with the data for a contact entry in the list.
586 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700587 private static class ViewEntry implements Collapsible<ViewEntry> {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700588 public int type = -1;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700589 public String kindAndType;
590 public String kind;
591 public String typeString;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700592 public String data;
593 public Uri uri;
594 public long id = 0;
595 public int maxLines = 1;
596 public String mimetype;
597
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700598 public Context context = null;
599 public String resPackageName = null;
600 public int actionIcon = -1;
601 public boolean isPrimary = false;
602 public int secondaryActionIcon = -1;
603 public Intent intent;
604 public Intent secondaryIntent = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700605 public ArrayList<Long> ids = new ArrayList<Long>();
606 public int collapseCount = 0;
607
608 public int presence = -1;
609
610 public CharSequence footerLine = null;
611
612 private ViewEntry() {
613 }
614
615 /**
616 * Build new {@link ViewEntry} and populate from the given values.
617 */
618 public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
Daniel Lehmann16249642010-09-10 19:56:51 -0700619 long dataId, ContentValues values) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700620 final ViewEntry entry = new ViewEntry();
621 entry.context = context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700622 entry.id = dataId;
623 entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
624 entry.mimetype = mimeType;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700625 entry.kindAndType = buildActionString(kind, values, context);
Dmitri Plotnikov5b201472010-10-13 10:21:08 -0700626 entry.kind = (kind.titleRes == -1 || kind.titleRes == 0) ? ""
Daniel Lehmanna4723252010-10-13 11:17:45 -0700627 : context.getString(kind.titleRes).toUpperCase();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700628 entry.data = buildDataString(kind, values, context);
629
630 if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
631 entry.type = values.getAsInteger(kind.typeColumn);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700632
633 // get type string
634 entry.typeString = "";
635 for (EditType type : kind.typeList) {
636 if (type.rawValue == entry.type) {
637 if (type.customColumn == null) {
638 // Non-custom type. Get its description from the resource
639 entry.typeString = context.getString(type.labelRes);
640 } else {
641 // Custom type. Read it from the database
642 entry.typeString = values.getAsString(type.customColumn);
643 }
644 break;
645 }
646 }
647 } else {
648 entry.typeString = "";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700649 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700650
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700651 if (kind.iconRes > 0) {
652 entry.resPackageName = kind.resPackageName;
653 entry.actionIcon = kind.iconRes;
654 }
655
656 return entry;
657 }
658
659 /**
660 * Apply given {@link DataStatus} values over this {@link ViewEntry}
661 *
662 * @param fillData When true, the given status replaces {@link #data}
663 * and {@link #footerLine}. Otherwise only {@link #presence}
664 * is updated.
665 */
666 public ViewEntry applyStatus(DataStatus status, boolean fillData) {
667 presence = status.getPresence();
668 if (fillData && status.isValid()) {
669 this.data = status.getStatus().toString();
670 this.footerLine = status.getTimestampLabel(context);
671 }
672
673 return this;
674 }
675
Daniel Lehmann16249642010-09-10 19:56:51 -0700676 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700677 public boolean collapseWith(ViewEntry entry) {
678 // assert equal collapse keys
679 if (!shouldCollapseWith(entry)) {
680 return false;
681 }
682
683 // Choose the label associated with the highest type precedence.
684 if (TypePrecedence.getTypePrecedence(mimetype, type)
685 > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
686 type = entry.type;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700687 kindAndType = entry.kindAndType;
688 kind = entry.kind;
689 typeString = entry.typeString;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700690 }
691
692 // Choose the max of the maxLines and maxLabelLines values.
693 maxLines = Math.max(maxLines, entry.maxLines);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700694
695 // Choose the presence with the highest precedence.
696 if (StatusUpdates.getPresencePrecedence(presence)
697 < StatusUpdates.getPresencePrecedence(entry.presence)) {
698 presence = entry.presence;
699 }
700
701 // If any of the collapsed entries are primary make the whole thing primary.
702 isPrimary = entry.isPrimary ? true : isPrimary;
703
704 // uri, and contactdId, shouldn't make a difference. Just keep the original.
705
706 // Keep track of all the ids that have been collapsed with this one.
707 ids.add(entry.id);
708 collapseCount++;
709 return true;
710 }
711
Daniel Lehmann16249642010-09-10 19:56:51 -0700712 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700713 public boolean shouldCollapseWith(ViewEntry entry) {
714 if (entry == null) {
715 return false;
716 }
717
718 if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
719 entry.data)) {
720 return false;
721 }
722
723 if (!TextUtils.equals(mimetype, entry.mimetype)
724 || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
725 || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
726 || actionIcon != entry.actionIcon) {
727 return false;
728 }
729
730 return true;
731 }
732 }
733
734 /** Cache of the children views of a row */
735 private static class ViewCache {
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700736 public TextView kindAndType;
737 public TextView kind;
738 public TextView type;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700739 public TextView data;
740 public TextView footer;
741 public ImageView actionIcon;
742 public ImageView presenceIcon;
743 public ImageView primaryIcon;
744 public ImageView secondaryActionButton;
745 public View secondaryActionDivider;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700746 }
747
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700748 private final class ViewAdapter extends BaseAdapter {
Daniel Lehmann16249642010-09-10 19:56:51 -0700749 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700750 public View getView(int position, View convertView, ViewGroup parent) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700751 final ViewEntry entry = getEntry(position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700752 final View v;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700753 final ViewCache viewCache;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700754
755 // Check to see if we can reuse convertView
756 if (convertView != null) {
757 v = convertView;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700758 viewCache = (ViewCache) v.getTag();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700759 } else {
760 // Create a new view if needed
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700761 v = mInflater.inflate(R.layout.contact_detail_list_item, parent, false);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700762
763 // Cache the children
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700764 viewCache = new ViewCache();
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700765 viewCache.kindAndType = (TextView) v.findViewById(R.id.kind_and_type);
766 viewCache.kind = (TextView) v.findViewById(R.id.kind);
767 viewCache.type = (TextView) v.findViewById(R.id.type);
768 viewCache.data = (TextView) v.findViewById(R.id.data);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700769 viewCache.footer = (TextView) v.findViewById(R.id.footer);
770 viewCache.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
771 viewCache.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
772 viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
773 viewCache.secondaryActionButton = (ImageView) v.findViewById(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700774 R.id.secondary_action_button);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700775 viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700776 viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
777 v.setTag(viewCache);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700778 }
779
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700780 final ViewEntry previousEntry = position == 0 ? null : getEntry(position - 1);
781 final boolean isFirstOfItsKind =
782 previousEntry == null ? true : !previousEntry.kind.equals(entry.kind);
783
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700784 // Bind the data to the view
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700785 bindView(v, entry, isFirstOfItsKind);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700786 return v;
787 }
788
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700789 protected void bindView(View view, ViewEntry entry, boolean isFirstOfItsKind) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700790 final Resources resources = mContext.getResources();
791 ViewCache views = (ViewCache) view.getTag();
792
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700793 // Set the label. This is either a combination field or separate fields for kind of type
794 if (views.kindAndType != null) views.kindAndType.setText(entry.kindAndType);
795 if (views.kind != null) views.kind.setText(isFirstOfItsKind ? entry.kind : "");
796 if (views.type != null) views.type.setText(entry.typeString);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700797
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700798 // Set the content
799 final TextView content = views.data;
800 if (content != null) {
Bai Taobff55492010-09-26 13:27:36 +0800801 content.setText(entry.data);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700802 setMaxLines(content, entry.maxLines);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700803 }
804
805 // Set the footer
806 if (!TextUtils.isEmpty(entry.footerLine)) {
807 views.footer.setText(entry.footerLine);
808 views.footer.setVisibility(View.VISIBLE);
809 } else {
810 views.footer.setVisibility(View.GONE);
811 }
812
813 // Set the primary icon
814 views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
815
816 // Set the action icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700817 final ImageView action = views.actionIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700818 if (entry.actionIcon != -1) {
819 Drawable actionIcon;
820 if (entry.resPackageName != null) {
821 // Load external resources through PackageManager
822 actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
823 entry.actionIcon, null);
824 } else {
825 actionIcon = resources.getDrawable(entry.actionIcon);
826 }
827 action.setImageDrawable(actionIcon);
828 action.setVisibility(View.VISIBLE);
829 } else {
830 // Things should still line up as if there was an icon, so make it invisible
831 action.setVisibility(View.INVISIBLE);
832 }
833
834 // Set the presence icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700835 final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700836 mContext, entry.presence);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700837 final ImageView presenceIconView = views.presenceIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700838 if (presenceIcon != null) {
839 presenceIconView.setImageDrawable(presenceIcon);
840 presenceIconView.setVisibility(View.VISIBLE);
841 } else {
842 presenceIconView.setVisibility(View.GONE);
843 }
844
845 // Set the secondary action button
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700846 final ImageView secondaryActionView = views.secondaryActionButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700847 Drawable secondaryActionIcon = null;
848 if (entry.secondaryActionIcon != -1) {
849 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
850 }
851 if (entry.secondaryIntent != null && secondaryActionIcon != null) {
852 secondaryActionView.setImageDrawable(secondaryActionIcon);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700853 secondaryActionView.setTag(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700854 secondaryActionView.setVisibility(View.VISIBLE);
855 views.secondaryActionDivider.setVisibility(View.VISIBLE);
856 } else {
857 secondaryActionView.setVisibility(View.GONE);
858 views.secondaryActionDivider.setVisibility(View.GONE);
859 }
860 }
861
862 private void setMaxLines(TextView textView, int maxLines) {
863 if (maxLines == 1) {
864 textView.setSingleLine(true);
865 textView.setEllipsize(TextUtils.TruncateAt.END);
866 } else {
867 textView.setSingleLine(false);
868 textView.setMaxLines(maxLines);
869 textView.setEllipsize(null);
870 }
871 }
872
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700873 private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
Daniel Lehmann16249642010-09-10 19:56:51 -0700874 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700875 public void onClick(View v) {
876 if (mListener == null) return;
877 if (v == null) return;
878 final ViewEntry entry = (ViewEntry) v.getTag();
879 if (entry == null) return;
880 final Intent intent = entry.secondaryIntent;
881 if (intent == null) return;
882 mListener.onItemClicked(intent);
883 }
884 };
885
Daniel Lehmann16249642010-09-10 19:56:51 -0700886 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700887 public int getCount() {
888 int count = 0;
889 final int numSections = mSections.size();
890 for (int i = 0; i < numSections; i++) {
891 final ArrayList<ViewEntry> section = mSections.get(i);
892 count += section.size();
893 }
894 return count;
895 }
896
Daniel Lehmann16249642010-09-10 19:56:51 -0700897 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700898 public Object getItem(int position) {
899 return getEntry(position);
900 }
901
Daniel Lehmann16249642010-09-10 19:56:51 -0700902 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700903 public long getItemId(int position) {
904 final ViewEntry entry = getEntry(position);
905 if (entry != null) {
906 return entry.id;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700907 }
Daniel Lehmann16249642010-09-10 19:56:51 -0700908 return -1;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700909 }
910
911 private ViewEntry getEntry(int position) {
912 final int numSections = mSections.size();
913 for (int i = 0; i < numSections; i++) {
914 final ArrayList<ViewEntry> section = mSections.get(i);
915 final int sectionSize = section.size();
916 if (position < sectionSize) {
917 return section.get(position);
918 }
919 position -= sectionSize;
920 }
921 return null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700922 }
923 }
924
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200925 @Override
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700926 public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700927 inflater.inflate(R.menu.view, menu);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700928 }
929
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200930 @Override
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700931 public void onPrepareOptionsMenu(Menu menu) {
Daniel Lehmann9fe582e2010-10-08 21:15:18 -0700932 boolean isDirectoryEntry = mContactData != null && mContactData.isDirectoryEntry();
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700933
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700934 // Options only shows telephony-related settings (ringtone, send to voicemail).
935 // ==> Hide if we don't have a telephone
936 final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
Daniel Lehmann7779f492010-08-24 15:26:36 -0700937 final boolean deviceHasPhone = PhoneCapabilityTester.isPhone(mContext);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700938 optionsMenu.setVisible(!isDirectoryEntry && deviceHasPhone);
939
940 final MenuItem editMenu = menu.findItem(R.id.menu_edit);
941 editMenu.setVisible(!isDirectoryEntry);
942
943 final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
944 deleteMenu.setVisible(!isDirectoryEntry);
945
946 final MenuItem shareMenu = menu.findItem(R.id.menu_share);
947 shareMenu.setVisible(!isDirectoryEntry && !mAllRestricted);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700948 }
949
950 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700951 public boolean onOptionsItemSelected(MenuItem item) {
952 switch (item.getItemId()) {
953 case R.id.menu_edit: {
Daniel Lehmann99eefbe2010-08-24 16:45:03 -0700954 if (mListener != null) mListener.onEditRequested(mLookupUri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700955 break;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700956 }
957 case R.id.menu_delete: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700958 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700959 return true;
960 }
961 case R.id.menu_options: {
Daniel Lehmann9fe582e2010-10-08 21:15:18 -0700962 if (mContactData == null) return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700963 final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
964 intent.setData(mContactData.getLookupUri());
965 mContext.startActivity(intent);
966 return true;
967 }
968 case R.id.menu_share: {
969 if (mAllRestricted) return false;
Daniel Lehmann9fe582e2010-10-08 21:15:18 -0700970 if (mContactData == null) return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700971
972 final String lookupKey = mContactData.getLookupKey();
973 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
974
975 final Intent intent = new Intent(Intent.ACTION_SEND);
976 intent.setType(Contacts.CONTENT_VCARD_TYPE);
977 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
978
979 // Launch chooser to share contact via
980 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
981 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
982
983 try {
984 mContext.startActivity(chooseIntent);
985 } catch (ActivityNotFoundException ex) {
986 Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
987 }
988 return true;
989 }
990 }
991 return false;
992 }
993
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700994 private void makePersonalCopy() {
995 if (mListener == null) {
996 return;
997 }
998
999 int exportSupport = mContactData.getDirectoryExportSupport();
1000 switch (exportSupport) {
1001 case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY: {
1002 createCopy(new Account(mContactData.getDirectoryAccountName(),
1003 mContactData.getDirectoryAccountType()));
1004 break;
1005 }
1006 case Directory.EXPORT_SUPPORT_ANY_ACCOUNT: {
1007 final ArrayList<Account> accounts = Sources.getInstance(mContext).getAccounts(true);
1008 if (accounts.isEmpty()) {
1009 createCopy(null);
1010 return; // Don't show a dialog.
1011 }
1012
1013 // In the common case of a single writable account, auto-select
1014 // it without showing a dialog.
1015 if (accounts.size() == 1) {
1016 createCopy(accounts.get(0));
1017 return; // Don't show a dialog.
1018 }
1019
Daniel Lehmann4a62be42010-09-07 18:22:59 -07001020 final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment(true);
1021 dialog.setTargetFragment(this, 0);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001022 dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
1023 break;
1024 }
1025 }
1026 }
1027
1028 @Override
1029 public void onAccountSelectorCancelled() {
1030 }
1031
1032 @Override
1033 public void onAccountChosen(Account account, boolean isNewContact) {
1034 createCopy(account);
1035 }
1036
1037 private void createCopy(Account account) {
1038 if (mListener != null) {
1039 mListener.onCreateRawContactRequested(mContactData.getContentValues(), account);
1040 }
1041 }
1042
Dmitri Plotnikovd3643142010-07-09 13:49:07 -07001043 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001044 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1045 AdapterView.AdapterContextMenuInfo info;
1046 try {
1047 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
1048 } catch (ClassCastException e) {
1049 Log.e(TAG, "bad menuInfo", e);
1050 return;
1051 }
1052
1053 // This can be null sometimes, don't crash...
1054 if (info == null) {
1055 Log.e(TAG, "bad menuInfo");
1056 return;
1057 }
1058
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001059 final ViewEntry entry = mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001060 menu.setHeaderTitle(R.string.contactOptionsTitle);
1061 if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
Daniel Lehmannf77b8e92010-07-16 18:58:36 -07001062 if (mHasPhone) {
1063 menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
1064 }
1065 if (mHasSms) {
1066 // If there is no Phone, SMS is the primary intent
1067 final Intent intent = mHasPhone ? entry.secondaryIntent : entry.intent;
1068 menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(intent);
1069 }
1070 if (!entry.isPrimary && mHasPhone) {
Daniel Lehmann16249642010-09-10 19:56:51 -07001071 menu.add(0, R.id.menu_detail_makeDefault, 0, R.string.menu_makeDefaultNumber);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001072 }
1073 } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
1074 menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
1075 if (!entry.isPrimary) {
Daniel Lehmann16249642010-09-10 19:56:51 -07001076 menu.add(0, R.id.menu_detail_makeDefault, 0, R.string.menu_makeDefaultEmail);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001077 }
1078 } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
1079 menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
1080 }
1081 }
1082
Daniel Lehmann16249642010-09-10 19:56:51 -07001083 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001084 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001085 if (mListener == null) return;
1086 final ViewEntry entry = mAdapter.getEntry(position);
Daniel Lehmannc3a00082010-04-13 13:53:54 -07001087 if (entry == null) return;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001088 final Intent intent = entry.intent;
1089 if (intent == null) return;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001090 mListener.onItemClicked(intent);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001091 }
1092
Dmitri Plotnikovd3643142010-07-09 13:49:07 -07001093 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001094 public boolean onContextItemSelected(MenuItem item) {
1095 switch (item.getItemId()) {
Daniel Lehmann16249642010-09-10 19:56:51 -07001096 case R.id.menu_detail_makeDefault: {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001097 if (makeItemDefault(item)) {
1098 return true;
1099 }
1100 break;
1101 }
1102 }
1103
1104 return false;
1105 }
1106
1107 private boolean makeItemDefault(MenuItem item) {
1108 ViewEntry entry = getViewEntryForMenuItem(item);
1109 if (entry == null) {
1110 return false;
1111 }
1112
1113 // Update the primary values in the data record.
1114 ContentValues values = new ContentValues(1);
1115 values.put(Data.IS_SUPER_PRIMARY, 1);
1116
1117 mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
1118 values, null, null);
1119 return true;
1120 }
1121
1122 private ViewEntry getViewEntryForMenuItem(MenuItem item) {
1123 AdapterView.AdapterContextMenuInfo info;
1124 try {
1125 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
1126 } catch (ClassCastException e) {
1127 Log.e(TAG, "bad menuInfo", e);
1128 return null;
1129 }
1130
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001131 return mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001132 }
1133
Daniel Lehmann16249642010-09-10 19:56:51 -07001134 public boolean handleKeyDown(int keyCode) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001135 switch (keyCode) {
1136 case KeyEvent.KEYCODE_CALL: {
1137 try {
1138 ITelephony phone = ITelephony.Stub.asInterface(
1139 ServiceManager.checkService("phone"));
1140 if (phone != null && !phone.isIdle()) {
1141 // Skip out and let the key be handled at a higher level
1142 break;
1143 }
1144 } catch (RemoteException re) {
1145 // Fall through and try to call the contact
1146 }
1147
1148 int index = mListView.getSelectedItemPosition();
1149 if (index != -1) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001150 final ViewEntry entry = mAdapter.getEntry(index);
Daniel Lehmann0deb6d32010-08-05 11:37:36 -07001151 if (entry != null && entry.intent != null &&
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001152 entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
1153 mContext.startActivity(entry.intent);
1154 return true;
1155 }
1156 } else if (mPrimaryPhoneUri != null) {
1157 // There isn't anything selected, call the default number
1158 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1159 mPrimaryPhoneUri);
1160 mContext.startActivity(intent);
1161 return true;
1162 }
1163 return false;
1164 }
1165
1166 case KeyEvent.KEYCODE_DEL: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001167 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001168 return true;
1169 }
1170 }
1171
Daniel Lehmannc2687c32010-04-19 18:20:44 -07001172 return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001173 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001174
Daniel Lehmann222744e2010-07-12 16:28:06 -07001175 /**
1176 * The listener for the detail loader
1177 */
1178 private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
1179 new LoaderCallbacks<ContactLoader.Result>() {
1180 @Override
1181 public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001182 return new ContactLoader(mContext, mLookupUri, true /* loadGroupMetaData */);
Daniel Lehmann222744e2010-07-12 16:28:06 -07001183 }
1184
1185 @Override
1186 public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
Dmitri Plotnikov5da93632010-09-18 13:09:26 -07001187 if (data == ContactLoader.Result.NOT_FOUND || data == ContactLoader.Result.ERROR) {
Daniel Lehmann222744e2010-07-12 16:28:06 -07001188 // Item has been deleted
1189 Log.i(TAG, "No contact found. Closing activity");
Daniel Lehmann99eefbe2010-08-24 16:45:03 -07001190 if (mListener != null) mListener.onContactNotFound();
Daniel Lehmann222744e2010-07-12 16:28:06 -07001191 return;
1192 }
1193 mContactData = data;
1194 bindData();
1195 }
1196 };
1197
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001198 private ContactDetailHeaderView.Listener mHeaderViewListener =
1199 new ContactDetailHeaderView.Listener() {
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001200 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001201 public void onDisplayNameClick(View view) {
1202 }
1203
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001204 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001205 public void onPhotoClick(View view) {
1206 }
1207 };
1208
1209
1210 public static interface Listener {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001211 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001212 * Contact was not found, so somehow close this fragment. This is raised after a contact
1213 * is removed via Menu/Delete
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001214 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001215 public void onContactNotFound();
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001216
1217 /**
1218 * User decided to go to Edit-Mode
1219 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001220 public void onEditRequested(Uri lookupUri);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001221
1222 /**
1223 * User clicked a single item (e.g. mail)
1224 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001225 public void onItemClicked(Intent intent);
1226
1227 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001228 * User decided to delete the contact
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001229 */
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001230 public void onDeleteRequested(Uri lookupUri);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001231
1232 /**
1233 * User requested creation of a new contact with the specified values.
1234 *
1235 * @param values ContentValues containing data rows for the new contact.
1236 * @param account Account where the new contact should be created
1237 */
1238 public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001239 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001240}