blob: 365851fb5726721465f88ac8d9d1b0d4c5057dfe [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 Lehmann35769b82010-10-18 16:33:29 -070027import com.android.contacts.model.BaseAccountType;
28import com.android.contacts.model.BaseAccountType.DataKind;
29import com.android.contacts.model.BaseAccountType.EditType;
30import com.android.contacts.model.AccountTypes;
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;
Daniel Lehmann0540f8b2010-10-28 16:55:00 -070070import android.provider.ContactsContract.CommonDataKinds.Relation;
David Brown4423ca52010-08-30 18:56:37 -070071import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070072import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070073import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
74import android.provider.ContactsContract.CommonDataKinds.Website;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070075import android.provider.ContactsContract.Contacts;
76import android.provider.ContactsContract.Data;
77import android.provider.ContactsContract.Directory;
78import android.provider.ContactsContract.DisplayNameSources;
Bai Tao09eb04f2010-09-01 15:34:16 +080079import android.provider.ContactsContract.PhoneLookup;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070080import android.provider.ContactsContract.RawContacts;
81import android.provider.ContactsContract.StatusUpdates;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070082import android.telephony.PhoneNumberUtils;
83import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070084import android.util.Log;
85import android.view.ContextMenu;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070086import android.view.ContextMenu.ContextMenuInfo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087import android.view.KeyEvent;
88import android.view.LayoutInflater;
89import android.view.Menu;
90import android.view.MenuInflater;
91import android.view.MenuItem;
92import android.view.View;
Daniel Lehmannc2687c32010-04-19 18:20:44 -070093import android.view.View.OnClickListener;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070094import android.view.View.OnCreateContextMenuListener;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070095import android.view.ViewGroup;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070096import android.widget.AdapterView;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070097import android.widget.AdapterView.OnItemClickListener;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070098import android.widget.BaseAdapter;
Daniel Lehmann18db43d2010-09-30 18:16:59 -070099import android.widget.Button;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700100import android.widget.ImageView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700101import android.widget.ListView;
102import android.widget.TextView;
103import android.widget.Toast;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104
105import java.util.ArrayList;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700106import java.util.Collections;
107import java.util.List;
Dmitri Plotnikov45b44022010-10-15 18:12:09 -0700108import java.util.Locale;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700109
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700110public class ContactDetailFragment extends Fragment implements OnCreateContextMenuListener,
111 OnItemClickListener, SelectAccountDialogFragment.Listener {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700112 private static final String TAG = "ContactDetailFragment";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700113
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700114 private static final int LOADER_DETAILS = 1;
115
Daniel Lehmann25a02822010-05-11 15:52:43 -0700116 private Context mContext;
117 private Uri mLookupUri;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700118 private Listener mListener;
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700119
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700120 private ContactLoader.Result mContactData;
121 private ContactDetailHeaderView mHeaderView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700122 private ListView mListView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700123 private ViewAdapter mAdapter;
124 private Uri mPrimaryPhoneUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700125
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700126 private Button mCopyGalToLocalButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700127 private boolean mAllRestricted;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700128 private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700129 private int mNumPhoneNumbers = 0;
Bai Tao09eb04f2010-09-01 15:34:16 +0800130 private String mDefaultCountryIso;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700131
132 /**
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700133 * Device capability: Set during buildEntries and used in the long-press context menu
134 */
135 private boolean mHasPhone;
136
137 /**
138 * Device capability: Set during buildEntries and used in the long-press context menu
139 */
140 private boolean mHasSms;
141
142 /**
Daniel Lehmann7781ea12010-10-21 21:56:43 -0700143 * Device capability: Set during buildEntries and used in the long-press context menu
144 */
145 private boolean mHasSip;
146
147 /**
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700148 * The view shown if the detail list is empty.
149 * We set this to the list view when first bind the adapter, so that it won't be shown while
150 * we're loading data.
151 */
152 private View mEmptyView;
153
154 /**
155 * A list of distinct contact IDs included in the current contact.
156 */
157 private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
158 private ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
159 private ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
160 private ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
161 private ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
162 private ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
163 private ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700164 private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
Daniel Lehmann0540f8b2010-10-28 16:55:00 -0700165 private ArrayList<ViewEntry> mRelationEntries = new ArrayList<ViewEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700166 private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
167 private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700168 private LayoutInflater mInflater;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700169
Daniel Lehmann25a02822010-05-11 15:52:43 -0700170 public ContactDetailFragment() {
171 // Explicit constructor for inflation
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700172
173 // Build the list of sections. The order they're added to mSections dictates the
174 // order they are displayed in the list.
175 mSections.add(mPhoneEntries);
176 mSections.add(mSmsEntries);
177 mSections.add(mEmailEntries);
178 mSections.add(mImEntries);
179 mSections.add(mPostalEntries);
180 mSections.add(mNicknameEntries);
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700181 mSections.add(mOtherEntries);
Daniel Lehmann0540f8b2010-10-28 16:55:00 -0700182 mSections.add(mRelationEntries);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700183 mSections.add(mGroupEntries);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700184 }
185
186 @Override
187 public void onAttach(Activity activity) {
188 super.onAttach(activity);
189 mContext = activity;
Bai Tao09eb04f2010-09-01 15:34:16 +0800190 mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(mContext);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700191 }
192
193 @Override
Dianne Hackborn84df2b62010-05-12 19:04:42 -0700194 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700195 final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700196
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700197 setHasOptionsMenu(true);
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700198
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700199 mInflater = inflater;
200
201 mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700202 mHeaderView.setListener(mHeaderViewListener);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700203
204 mListView = (ListView) view.findViewById(android.R.id.list);
205 mListView.setOnCreateContextMenuListener(this);
206 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
207 mListView.setOnItemClickListener(this);
208 // Don't set it to mListView yet. We do so later when we bind the adapter.
209 mEmptyView = view.findViewById(android.R.id.empty);
210
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700211 mCopyGalToLocalButton = (Button) view.findViewById(R.id.copyLocal);
212 mCopyGalToLocalButton.setOnClickListener(new OnClickListener() {
213 @Override
214 public void onClick(View v) {
215 makePersonalCopy();
216 }
217 });
218
Daniel Lehmann25a02822010-05-11 15:52:43 -0700219 return view;
220 }
221
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700222 public void setListener(Listener value) {
223 mListener = value;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700224 }
225
Dmitri Plotnikov9cd17082010-07-09 14:10:04 -0700226 public Uri getUri() {
227 return mLookupUri;
228 }
229
Daniel Lehmann222744e2010-07-12 16:28:06 -0700230 @Override
231 public void onActivityCreated(Bundle savedInstanceState) {
232 super.onActivityCreated(savedInstanceState);
233
234 getLoaderManager().initLoader(LOADER_DETAILS, null, mDetailLoaderListener);
235 }
236
Daniel Lehmann25a02822010-05-11 15:52:43 -0700237 public void loadUri(Uri lookupUri) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700238 mLookupUri = lookupUri;
Daniel Lehmann222744e2010-07-12 16:28:06 -0700239 if (getActivity() != null) {
240 getLoaderManager().restartLoader(LOADER_DETAILS, null, mDetailLoaderListener);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700241 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700242 }
243
244 private void bindData() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700245 // Set the header
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700246 mHeaderView.loadData(mContactData);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700247
248 // Build up the contact entries
249 buildEntries();
250
251 // Collapse similar data items in select sections.
252 Collapser.collapseList(mPhoneEntries);
253 Collapser.collapseList(mSmsEntries);
254 Collapser.collapseList(mEmailEntries);
255 Collapser.collapseList(mPostalEntries);
256 Collapser.collapseList(mImEntries);
257
258 if (mAdapter == null) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700259 mAdapter = new ViewAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700260 mListView.setAdapter(mAdapter);
261 } else {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700262 mAdapter.notifyDataSetChanged();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700263 }
264 mListView.setEmptyView(mEmptyView);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700265
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700266 // Configure copy gal button
267 if (mContactData.isDirectoryEntry()) {
268 final int exportSupport = mContactData.getDirectoryExportSupport();
269 if (exportSupport == Directory.EXPORT_SUPPORT_ANY_ACCOUNT
270 || exportSupport == Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
271 mCopyGalToLocalButton.setVisibility(View.VISIBLE);
272 } else {
273 mCopyGalToLocalButton.setVisibility(View.GONE);
274 }
275 } else {
276 mCopyGalToLocalButton.setVisibility(View.GONE);
277 }
278
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700279 getActivity().invalidateOptionsMenu();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700280 }
281
282 /**
283 * Build up the entries to display on the screen.
284 */
285 private final void buildEntries() {
Daniel Lehmann7779f492010-08-24 15:26:36 -0700286 mHasPhone = PhoneCapabilityTester.isPhone(mContext);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700287 mHasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
Daniel Lehmann7781ea12010-10-21 21:56:43 -0700288 mHasSip = PhoneCapabilityTester.isSipPhone(mContext);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700289
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700290 // Clear out the old entries
291 final int numSections = mSections.size();
292 for (int i = 0; i < numSections; i++) {
293 mSections.get(i).clear();
294 }
295
296 mRawContactIds.clear();
297
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700298 mAllRestricted = true;
299 mPrimaryPhoneUri = null;
300 mNumPhoneNumbers = 0;
301
302 mWritableRawContactIds.clear();
303
Daniel Lehmann35769b82010-10-18 16:33:29 -0700304 final AccountTypes sources = AccountTypes.getInstance(mContext);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700305
306 // Build up method entries
307 if (mContactData == null) {
308 return;
309 }
310
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700311 ArrayList<String> groups = new ArrayList<String>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700312 for (Entity entity: mContactData.getEntities()) {
313 final ContentValues entValues = entity.getEntityValues();
314 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
315 final long rawContactId = entValues.getAsLong(RawContacts._ID);
316
317 // Mark when this contact has any unrestricted components
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700318 Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
319 final boolean isRestricted = restricted != null && restricted != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700320 if (!isRestricted) mAllRestricted = false;
321
322 if (!mRawContactIds.contains(rawContactId)) {
323 mRawContactIds.add(rawContactId);
324 }
Daniel Lehmann35769b82010-10-18 16:33:29 -0700325 BaseAccountType contactsSource = sources.getInflatedSource(accountType,
326 BaseAccountType.LEVEL_SUMMARY);
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700327 if (contactsSource == null || !contactsSource.readOnly) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700328 mWritableRawContactIds.add(rawContactId);
329 }
330
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700331 for (NamedContentValues subValue : entity.getSubValues()) {
332 final ContentValues entryValues = subValue.values;
333 entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
334
335 final long dataId = entryValues.getAsLong(Data._ID);
336 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
337 if (mimeType == null) continue;
338
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700339 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
340 Long groupId = entryValues.getAsLong(GroupMembership.GROUP_ROW_ID);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700341 if (groupId != null) {
342 handleGroupMembership(groups, mContactData.getGroupMetaData(), groupId);
343 }
344 continue;
345 }
346
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700347 final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
Daniel Lehmann35769b82010-10-18 16:33:29 -0700348 BaseAccountType.LEVEL_CONSTRAINTS);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700349 if (kind == null) continue;
350
Daniel Lehmann16249642010-09-10 19:56:51 -0700351 final ViewEntry entry = ViewEntry.fromValues(mContext, mimeType, kind, dataId,
352 entryValues);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700353
354 final boolean hasData = !TextUtils.isEmpty(entry.data);
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700355 Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
356 final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700357
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700358 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
359 // Always ignore the name. It is shown in the header if set
360 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700361 // Build phone entries
362 mNumPhoneNumbers++;
Bai Tao09eb04f2010-09-01 15:34:16 +0800363 String phoneNumberE164 =
364 entryValues.getAsString(PhoneLookup.NORMALIZED_NUMBER);
365 entry.data = PhoneNumberUtils.formatNumber(
366 entry.data, phoneNumberE164, mDefaultCountryIso);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700367 final Intent phoneIntent = mHasPhone ? new Intent(Intent.ACTION_CALL_PRIVILEGED,
368 Uri.fromParts(Constants.SCHEME_TEL, entry.data, null)) : null;
369 final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
370 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
371
372 // Configure Icons and Intents. Notice actionIcon is already set to the phone
373 if (mHasPhone && mHasSms) {
374 entry.intent = phoneIntent;
375 entry.secondaryIntent = smsIntent;
376 entry.secondaryActionIcon = kind.iconAltRes;
377 } else if (mHasPhone) {
378 entry.intent = phoneIntent;
379 } else if (mHasSms) {
380 entry.intent = smsIntent;
381 entry.actionIcon = kind.iconAltRes;
382 } else {
383 entry.intent = null;
384 entry.actionIcon = -1;
385 }
386
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700387
388 // Remember super-primary phone
389 if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
390
391 entry.isPrimary = isSuperPrimary;
392 mPhoneEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700393 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
394 // Build email entries
395 entry.intent = new Intent(Intent.ACTION_SENDTO,
396 Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
397 entry.isPrimary = isSuperPrimary;
398 mEmailEntries.add(entry);
399
400 // When Email rows have status, create additional Im row
401 final DataStatus status = mContactData.getStatuses().get(entry.id);
402 if (status != null) {
403 final String imMime = Im.CONTENT_ITEM_TYPE;
404 final DataKind imKind = sources.getKindOrFallback(accountType,
Daniel Lehmann35769b82010-10-18 16:33:29 -0700405 imMime, mContext, BaseAccountType.LEVEL_MIMETYPES);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700406 final ViewEntry imEntry = ViewEntry.fromValues(mContext,
Daniel Lehmann16249642010-09-10 19:56:51 -0700407 imMime, imKind, dataId, entryValues);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700408 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
409 if (imActions != null) {
410 imEntry.actionIcon = imActions.getPrimaryActionIcon();
411 imEntry.secondaryActionIcon = imActions.getSecondaryActionIcon();
412 imEntry.intent = imActions.getPrimaryIntent();
413 imEntry.secondaryIntent = imActions.getSecondaryIntent();
414 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700415 imEntry.applyStatus(status, false);
416 mImEntries.add(imEntry);
417 }
418 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
419 // Build postal entries
420 entry.maxLines = 4;
421 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
422 mPostalEntries.add(entry);
423 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
424 // Build IM entries
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700425 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
426 if (imActions != null) {
427 entry.actionIcon = imActions.getPrimaryActionIcon();
428 entry.secondaryActionIcon = imActions.getSecondaryActionIcon();
429 entry.intent = imActions.getPrimaryIntent();
430 entry.secondaryIntent = imActions.getSecondaryIntent();
431 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700432 if (TextUtils.isEmpty(entry.kindAndType)) {
433 entry.kindAndType = mContext.getString(R.string.chat).toLowerCase();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700434 }
435
436 // Apply presence and status details when available
437 final DataStatus status = mContactData.getStatuses().get(entry.id);
438 if (status != null) {
439 entry.applyStatus(status, false);
440 }
441 mImEntries.add(entry);
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700442 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
443 // Organizations are not shown. The first one is shown in the header
444 // and subsequent ones are not supported anymore
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700445 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
446 // Build nickname entries
447 final boolean isNameRawContact =
448 (mContactData.getNameRawContactId() == rawContactId);
449
450 final boolean duplicatesTitle =
451 isNameRawContact
452 && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
453
454 if (!duplicatesTitle) {
455 entry.uri = null;
456 mNicknameEntries.add(entry);
457 }
458 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
459 // Build note entries
460 entry.uri = null;
461 entry.maxLines = 100;
462 mOtherEntries.add(entry);
463 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
David Brown4423ca52010-08-30 18:56:37 -0700464 // Build Website entries
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700465 entry.uri = null;
466 entry.maxLines = 10;
467 try {
468 WebAddress webAddress = new WebAddress(entry.data);
469 entry.intent = new Intent(Intent.ACTION_VIEW,
470 Uri.parse(webAddress.toString()));
471 } catch (ParseException e) {
472 Log.e(TAG, "Couldn't parse website: " + entry.data);
473 }
474 mOtherEntries.add(entry);
David Brown4423ca52010-08-30 18:56:37 -0700475 } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
476 // Build SipAddress entries
477 entry.uri = null;
478 entry.maxLines = 1;
Daniel Lehmann7781ea12010-10-21 21:56:43 -0700479 if (mHasSip) {
480 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
481 Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
482 } else {
483 entry.intent = null;
484 entry.actionIcon = -1;
485 }
David Brown4423ca52010-08-30 18:56:37 -0700486 mOtherEntries.add(entry);
487 // TODO: Consider moving the SipAddress into its own
488 // section (rather than lumping it in with mOtherEntries)
489 // so that we can reposition it right under the phone number.
Daniel Lehmann35769b82010-10-18 16:33:29 -0700490 // (Then, we'd also update FallbackAccountType.java to set
David Brown4423ca52010-08-30 18:56:37 -0700491 // secondary=false for this field, and tweak the weight
492 // of its DataKind.)
Dmitri Plotnikov45b44022010-10-15 18:12:09 -0700493 } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
494 entry.data = DateUtils.formatDate(mContext, entry.data);
495 entry.uri = null;
496 mOtherEntries.add(entry);
Daniel Lehmann0540f8b2010-10-28 16:55:00 -0700497 } else if (Relation.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
498 mRelationEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700499 } else {
500 // Handle showing custom rows
501 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
502
503 // Use social summary when requested by external source
504 final DataStatus status = mContactData.getStatuses().get(entry.id);
505 final boolean hasSocial = kind.actionBodySocial && status != null;
506 if (hasSocial) {
507 entry.applyStatus(status, true);
508 }
509
510 if (hasSocial || hasData) {
511 mOtherEntries.add(entry);
512 }
513 }
514 }
515 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700516
517 if (!groups.isEmpty()) {
518 ViewEntry entry = new ViewEntry();
519 Collections.sort(groups);
520 StringBuilder sb = new StringBuilder();
521 int size = groups.size();
522 for (int i = 0; i < size; i++) {
523 if (i != 0) {
524 sb.append(", ");
525 }
526 sb.append(groups.get(i));
527 }
528 entry.mimetype = GroupMembership.MIMETYPE;
529 entry.kind = mContext.getString(R.string.groupsLabel);
530 entry.data = sb.toString();
531 mGroupEntries.add(entry);
532 }
533 }
534
535 /**
536 * Maps group ID to the corresponding group name, collapses all synonymous groups.
537 * Ignores default groups (e.g. My Contacts) and favorites groups.
538 */
539 private void handleGroupMembership(
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700540 ArrayList<String> groups, List<GroupMetaData> groupMetaData, long groupId) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700541 if (groupMetaData == null) {
542 return;
543 }
544
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700545 for (GroupMetaData group : groupMetaData) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700546 if (group.getGroupId() == groupId) {
547 if (!group.isDefaultGroup() && !group.isFavorites()) {
548 String title = group.getTitle();
549 if (!groups.contains(title)) {
550 groups.add(title);
551 }
552 }
553 break;
554 }
555 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700556 }
557
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700558 private static String buildActionString(DataKind kind, ContentValues values, Context context) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700559 if (kind.actionHeader == null) {
560 return null;
561 }
562 CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
563 if (actionHeader == null) {
564 return null;
565 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700566 return actionHeader.toString();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700567 }
568
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700569 private static String buildDataString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700570 Context context) {
571 if (kind.actionBody == null) {
572 return null;
573 }
574 CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
575 return actionBody == null ? null : actionBody.toString();
576 }
577
578 /**
579 * A basic structure with the data for a contact entry in the list.
580 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700581 private static class ViewEntry implements Collapsible<ViewEntry> {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700582 public int type = -1;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700583 public String kindAndType;
584 public String kind;
585 public String typeString;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700586 public String data;
587 public Uri uri;
588 public long id = 0;
589 public int maxLines = 1;
590 public String mimetype;
591
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700592 public Context context = null;
593 public String resPackageName = null;
594 public int actionIcon = -1;
595 public boolean isPrimary = false;
596 public int secondaryActionIcon = -1;
597 public Intent intent;
598 public Intent secondaryIntent = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700599 public ArrayList<Long> ids = new ArrayList<Long>();
600 public int collapseCount = 0;
601
602 public int presence = -1;
603
604 public CharSequence footerLine = null;
605
606 private ViewEntry() {
607 }
608
609 /**
610 * Build new {@link ViewEntry} and populate from the given values.
611 */
612 public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
Daniel Lehmann16249642010-09-10 19:56:51 -0700613 long dataId, ContentValues values) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700614 final ViewEntry entry = new ViewEntry();
615 entry.context = context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700616 entry.id = dataId;
617 entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
618 entry.mimetype = mimeType;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700619 entry.kindAndType = buildActionString(kind, values, context);
Dmitri Plotnikov5b201472010-10-13 10:21:08 -0700620 entry.kind = (kind.titleRes == -1 || kind.titleRes == 0) ? ""
Daniel Lehmanna4723252010-10-13 11:17:45 -0700621 : context.getString(kind.titleRes).toUpperCase();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700622 entry.data = buildDataString(kind, values, context);
623
624 if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
625 entry.type = values.getAsInteger(kind.typeColumn);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700626
627 // get type string
628 entry.typeString = "";
629 for (EditType type : kind.typeList) {
630 if (type.rawValue == entry.type) {
631 if (type.customColumn == null) {
632 // Non-custom type. Get its description from the resource
633 entry.typeString = context.getString(type.labelRes);
634 } else {
635 // Custom type. Read it from the database
636 entry.typeString = values.getAsString(type.customColumn);
637 }
638 break;
639 }
640 }
641 } else {
642 entry.typeString = "";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700643 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700644
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700645 if (kind.iconRes > 0) {
646 entry.resPackageName = kind.resPackageName;
647 entry.actionIcon = kind.iconRes;
648 }
649
650 return entry;
651 }
652
653 /**
654 * Apply given {@link DataStatus} values over this {@link ViewEntry}
655 *
656 * @param fillData When true, the given status replaces {@link #data}
657 * and {@link #footerLine}. Otherwise only {@link #presence}
658 * is updated.
659 */
660 public ViewEntry applyStatus(DataStatus status, boolean fillData) {
661 presence = status.getPresence();
662 if (fillData && status.isValid()) {
663 this.data = status.getStatus().toString();
664 this.footerLine = status.getTimestampLabel(context);
665 }
666
667 return this;
668 }
669
Daniel Lehmann16249642010-09-10 19:56:51 -0700670 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700671 public boolean collapseWith(ViewEntry entry) {
672 // assert equal collapse keys
673 if (!shouldCollapseWith(entry)) {
674 return false;
675 }
676
677 // Choose the label associated with the highest type precedence.
678 if (TypePrecedence.getTypePrecedence(mimetype, type)
679 > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
680 type = entry.type;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700681 kindAndType = entry.kindAndType;
682 kind = entry.kind;
683 typeString = entry.typeString;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700684 }
685
686 // Choose the max of the maxLines and maxLabelLines values.
687 maxLines = Math.max(maxLines, entry.maxLines);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700688
689 // Choose the presence with the highest precedence.
690 if (StatusUpdates.getPresencePrecedence(presence)
691 < StatusUpdates.getPresencePrecedence(entry.presence)) {
692 presence = entry.presence;
693 }
694
695 // If any of the collapsed entries are primary make the whole thing primary.
696 isPrimary = entry.isPrimary ? true : isPrimary;
697
698 // uri, and contactdId, shouldn't make a difference. Just keep the original.
699
700 // Keep track of all the ids that have been collapsed with this one.
701 ids.add(entry.id);
702 collapseCount++;
703 return true;
704 }
705
Daniel Lehmann16249642010-09-10 19:56:51 -0700706 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700707 public boolean shouldCollapseWith(ViewEntry entry) {
708 if (entry == null) {
709 return false;
710 }
711
712 if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
713 entry.data)) {
714 return false;
715 }
716
717 if (!TextUtils.equals(mimetype, entry.mimetype)
718 || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
719 || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
720 || actionIcon != entry.actionIcon) {
721 return false;
722 }
723
724 return true;
725 }
726 }
727
728 /** Cache of the children views of a row */
729 private static class ViewCache {
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700730 public TextView kindAndType;
731 public TextView kind;
732 public TextView type;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700733 public TextView data;
734 public TextView footer;
735 public ImageView actionIcon;
736 public ImageView presenceIcon;
737 public ImageView primaryIcon;
738 public ImageView secondaryActionButton;
739 public View secondaryActionDivider;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700740 }
741
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700742 private final class ViewAdapter extends BaseAdapter {
Daniel Lehmann16249642010-09-10 19:56:51 -0700743 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700744 public View getView(int position, View convertView, ViewGroup parent) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700745 final ViewEntry entry = getEntry(position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700746 final View v;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700747 final ViewCache viewCache;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700748
749 // Check to see if we can reuse convertView
750 if (convertView != null) {
751 v = convertView;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700752 viewCache = (ViewCache) v.getTag();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700753 } else {
754 // Create a new view if needed
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700755 v = mInflater.inflate(R.layout.contact_detail_list_item, parent, false);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700756
757 // Cache the children
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700758 viewCache = new ViewCache();
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700759 viewCache.kindAndType = (TextView) v.findViewById(R.id.kind_and_type);
760 viewCache.kind = (TextView) v.findViewById(R.id.kind);
761 viewCache.type = (TextView) v.findViewById(R.id.type);
762 viewCache.data = (TextView) v.findViewById(R.id.data);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700763 viewCache.footer = (TextView) v.findViewById(R.id.footer);
764 viewCache.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
765 viewCache.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
766 viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
767 viewCache.secondaryActionButton = (ImageView) v.findViewById(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700768 R.id.secondary_action_button);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700769 viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700770 viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
771 v.setTag(viewCache);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700772 }
773
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700774 final ViewEntry previousEntry = position == 0 ? null : getEntry(position - 1);
775 final boolean isFirstOfItsKind =
776 previousEntry == null ? true : !previousEntry.kind.equals(entry.kind);
777
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700778 // Bind the data to the view
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700779 bindView(v, entry, isFirstOfItsKind);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700780 return v;
781 }
782
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700783 protected void bindView(View view, ViewEntry entry, boolean isFirstOfItsKind) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700784 final Resources resources = mContext.getResources();
785 ViewCache views = (ViewCache) view.getTag();
786
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700787 // Set the label. This is either a combination field or separate fields for kind of type
788 if (views.kindAndType != null) views.kindAndType.setText(entry.kindAndType);
789 if (views.kind != null) views.kind.setText(isFirstOfItsKind ? entry.kind : "");
790 if (views.type != null) views.type.setText(entry.typeString);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700791
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700792 // Set the content
793 final TextView content = views.data;
794 if (content != null) {
Bai Taobff55492010-09-26 13:27:36 +0800795 content.setText(entry.data);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700796 setMaxLines(content, entry.maxLines);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700797 }
798
799 // Set the footer
800 if (!TextUtils.isEmpty(entry.footerLine)) {
801 views.footer.setText(entry.footerLine);
802 views.footer.setVisibility(View.VISIBLE);
803 } else {
804 views.footer.setVisibility(View.GONE);
805 }
806
807 // Set the primary icon
808 views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
809
810 // Set the action icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700811 final ImageView action = views.actionIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700812 if (entry.actionIcon != -1) {
813 Drawable actionIcon;
814 if (entry.resPackageName != null) {
815 // Load external resources through PackageManager
816 actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
817 entry.actionIcon, null);
818 } else {
819 actionIcon = resources.getDrawable(entry.actionIcon);
820 }
821 action.setImageDrawable(actionIcon);
822 action.setVisibility(View.VISIBLE);
823 } else {
824 // Things should still line up as if there was an icon, so make it invisible
825 action.setVisibility(View.INVISIBLE);
826 }
827
828 // Set the presence icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700829 final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700830 mContext, entry.presence);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700831 final ImageView presenceIconView = views.presenceIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700832 if (presenceIcon != null) {
833 presenceIconView.setImageDrawable(presenceIcon);
834 presenceIconView.setVisibility(View.VISIBLE);
835 } else {
836 presenceIconView.setVisibility(View.GONE);
837 }
838
839 // Set the secondary action button
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700840 final ImageView secondaryActionView = views.secondaryActionButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700841 Drawable secondaryActionIcon = null;
842 if (entry.secondaryActionIcon != -1) {
843 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
844 }
845 if (entry.secondaryIntent != null && secondaryActionIcon != null) {
846 secondaryActionView.setImageDrawable(secondaryActionIcon);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700847 secondaryActionView.setTag(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700848 secondaryActionView.setVisibility(View.VISIBLE);
849 views.secondaryActionDivider.setVisibility(View.VISIBLE);
850 } else {
851 secondaryActionView.setVisibility(View.GONE);
852 views.secondaryActionDivider.setVisibility(View.GONE);
853 }
854 }
855
856 private void setMaxLines(TextView textView, int maxLines) {
857 if (maxLines == 1) {
858 textView.setSingleLine(true);
859 textView.setEllipsize(TextUtils.TruncateAt.END);
860 } else {
861 textView.setSingleLine(false);
862 textView.setMaxLines(maxLines);
863 textView.setEllipsize(null);
864 }
865 }
866
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700867 private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
Daniel Lehmann16249642010-09-10 19:56:51 -0700868 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700869 public void onClick(View v) {
870 if (mListener == null) return;
871 if (v == null) return;
872 final ViewEntry entry = (ViewEntry) v.getTag();
873 if (entry == null) return;
874 final Intent intent = entry.secondaryIntent;
875 if (intent == null) return;
876 mListener.onItemClicked(intent);
877 }
878 };
879
Daniel Lehmann16249642010-09-10 19:56:51 -0700880 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700881 public int getCount() {
882 int count = 0;
883 final int numSections = mSections.size();
884 for (int i = 0; i < numSections; i++) {
885 final ArrayList<ViewEntry> section = mSections.get(i);
886 count += section.size();
887 }
888 return count;
889 }
890
Daniel Lehmann16249642010-09-10 19:56:51 -0700891 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700892 public Object getItem(int position) {
893 return getEntry(position);
894 }
895
Daniel Lehmann16249642010-09-10 19:56:51 -0700896 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700897 public long getItemId(int position) {
898 final ViewEntry entry = getEntry(position);
899 if (entry != null) {
900 return entry.id;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700901 }
Daniel Lehmann16249642010-09-10 19:56:51 -0700902 return -1;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700903 }
904
905 private ViewEntry getEntry(int position) {
906 final int numSections = mSections.size();
907 for (int i = 0; i < numSections; i++) {
908 final ArrayList<ViewEntry> section = mSections.get(i);
909 final int sectionSize = section.size();
910 if (position < sectionSize) {
911 return section.get(position);
912 }
913 position -= sectionSize;
914 }
915 return null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700916 }
917 }
918
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200919 @Override
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700920 public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700921 inflater.inflate(R.menu.view, menu);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700922 }
923
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200924 @Override
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700925 public void onPrepareOptionsMenu(Menu menu) {
Daniel Lehmann9fe582e2010-10-08 21:15:18 -0700926 boolean isDirectoryEntry = mContactData != null && mContactData.isDirectoryEntry();
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700927
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700928 // Options only shows telephony-related settings (ringtone, send to voicemail).
929 // ==> Hide if we don't have a telephone
930 final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
Daniel Lehmann7779f492010-08-24 15:26:36 -0700931 final boolean deviceHasPhone = PhoneCapabilityTester.isPhone(mContext);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700932 optionsMenu.setVisible(!isDirectoryEntry && deviceHasPhone);
933
934 final MenuItem editMenu = menu.findItem(R.id.menu_edit);
935 editMenu.setVisible(!isDirectoryEntry);
936
937 final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
938 deleteMenu.setVisible(!isDirectoryEntry);
939
940 final MenuItem shareMenu = menu.findItem(R.id.menu_share);
941 shareMenu.setVisible(!isDirectoryEntry && !mAllRestricted);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700942 }
943
944 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700945 public boolean onOptionsItemSelected(MenuItem item) {
946 switch (item.getItemId()) {
947 case R.id.menu_edit: {
Daniel Lehmann99eefbe2010-08-24 16:45:03 -0700948 if (mListener != null) mListener.onEditRequested(mLookupUri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700949 break;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700950 }
951 case R.id.menu_delete: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700952 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700953 return true;
954 }
955 case R.id.menu_options: {
Daniel Lehmann9fe582e2010-10-08 21:15:18 -0700956 if (mContactData == null) return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700957 final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
958 intent.setData(mContactData.getLookupUri());
959 mContext.startActivity(intent);
960 return true;
961 }
962 case R.id.menu_share: {
963 if (mAllRestricted) return false;
Daniel Lehmann9fe582e2010-10-08 21:15:18 -0700964 if (mContactData == null) return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700965
966 final String lookupKey = mContactData.getLookupKey();
967 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
968
969 final Intent intent = new Intent(Intent.ACTION_SEND);
970 intent.setType(Contacts.CONTENT_VCARD_TYPE);
971 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
972
973 // Launch chooser to share contact via
974 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
975 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
976
977 try {
978 mContext.startActivity(chooseIntent);
979 } catch (ActivityNotFoundException ex) {
980 Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
981 }
982 return true;
983 }
984 }
985 return false;
986 }
987
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700988 private void makePersonalCopy() {
989 if (mListener == null) {
990 return;
991 }
992
993 int exportSupport = mContactData.getDirectoryExportSupport();
994 switch (exportSupport) {
995 case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY: {
996 createCopy(new Account(mContactData.getDirectoryAccountName(),
997 mContactData.getDirectoryAccountType()));
998 break;
999 }
1000 case Directory.EXPORT_SUPPORT_ANY_ACCOUNT: {
Daniel Lehmann35769b82010-10-18 16:33:29 -07001001 final ArrayList<Account> accounts = AccountTypes.getInstance(mContext).getAccounts(true);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001002 if (accounts.isEmpty()) {
1003 createCopy(null);
1004 return; // Don't show a dialog.
1005 }
1006
1007 // In the common case of a single writable account, auto-select
1008 // it without showing a dialog.
1009 if (accounts.size() == 1) {
1010 createCopy(accounts.get(0));
1011 return; // Don't show a dialog.
1012 }
1013
Daniel Lehmann4a62be42010-09-07 18:22:59 -07001014 final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment(true);
1015 dialog.setTargetFragment(this, 0);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001016 dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
1017 break;
1018 }
1019 }
1020 }
1021
1022 @Override
1023 public void onAccountSelectorCancelled() {
1024 }
1025
1026 @Override
1027 public void onAccountChosen(Account account, boolean isNewContact) {
1028 createCopy(account);
1029 }
1030
1031 private void createCopy(Account account) {
1032 if (mListener != null) {
1033 mListener.onCreateRawContactRequested(mContactData.getContentValues(), account);
1034 }
1035 }
1036
Dmitri Plotnikovd3643142010-07-09 13:49:07 -07001037 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001038 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1039 AdapterView.AdapterContextMenuInfo info;
1040 try {
1041 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
1042 } catch (ClassCastException e) {
1043 Log.e(TAG, "bad menuInfo", e);
1044 return;
1045 }
1046
1047 // This can be null sometimes, don't crash...
1048 if (info == null) {
1049 Log.e(TAG, "bad menuInfo");
1050 return;
1051 }
1052
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001053 final ViewEntry entry = mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001054 menu.setHeaderTitle(R.string.contactOptionsTitle);
1055 if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
Daniel Lehmannf77b8e92010-07-16 18:58:36 -07001056 if (mHasPhone) {
1057 menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
1058 }
1059 if (mHasSms) {
1060 // If there is no Phone, SMS is the primary intent
1061 final Intent intent = mHasPhone ? entry.secondaryIntent : entry.intent;
1062 menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(intent);
1063 }
1064 if (!entry.isPrimary && mHasPhone) {
Daniel Lehmann16249642010-09-10 19:56:51 -07001065 menu.add(0, R.id.menu_detail_makeDefault, 0, R.string.menu_makeDefaultNumber);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001066 }
1067 } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
1068 menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
1069 if (!entry.isPrimary) {
Daniel Lehmann16249642010-09-10 19:56:51 -07001070 menu.add(0, R.id.menu_detail_makeDefault, 0, R.string.menu_makeDefaultEmail);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001071 }
1072 } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
1073 menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
1074 }
1075 }
1076
Daniel Lehmann16249642010-09-10 19:56:51 -07001077 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001078 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001079 if (mListener == null) return;
1080 final ViewEntry entry = mAdapter.getEntry(position);
Daniel Lehmannc3a00082010-04-13 13:53:54 -07001081 if (entry == null) return;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001082 final Intent intent = entry.intent;
1083 if (intent == null) return;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001084 mListener.onItemClicked(intent);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001085 }
1086
Dmitri Plotnikovd3643142010-07-09 13:49:07 -07001087 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001088 public boolean onContextItemSelected(MenuItem item) {
1089 switch (item.getItemId()) {
Daniel Lehmann16249642010-09-10 19:56:51 -07001090 case R.id.menu_detail_makeDefault: {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001091 if (makeItemDefault(item)) {
1092 return true;
1093 }
1094 break;
1095 }
1096 }
1097
1098 return false;
1099 }
1100
1101 private boolean makeItemDefault(MenuItem item) {
1102 ViewEntry entry = getViewEntryForMenuItem(item);
1103 if (entry == null) {
1104 return false;
1105 }
1106
1107 // Update the primary values in the data record.
1108 ContentValues values = new ContentValues(1);
1109 values.put(Data.IS_SUPER_PRIMARY, 1);
1110
1111 mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
1112 values, null, null);
1113 return true;
1114 }
1115
1116 private ViewEntry getViewEntryForMenuItem(MenuItem item) {
1117 AdapterView.AdapterContextMenuInfo info;
1118 try {
1119 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
1120 } catch (ClassCastException e) {
1121 Log.e(TAG, "bad menuInfo", e);
1122 return null;
1123 }
1124
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001125 return mAdapter.getEntry(info.position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001126 }
1127
Daniel Lehmann16249642010-09-10 19:56:51 -07001128 public boolean handleKeyDown(int keyCode) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001129 switch (keyCode) {
1130 case KeyEvent.KEYCODE_CALL: {
1131 try {
1132 ITelephony phone = ITelephony.Stub.asInterface(
1133 ServiceManager.checkService("phone"));
1134 if (phone != null && !phone.isIdle()) {
1135 // Skip out and let the key be handled at a higher level
1136 break;
1137 }
1138 } catch (RemoteException re) {
1139 // Fall through and try to call the contact
1140 }
1141
1142 int index = mListView.getSelectedItemPosition();
1143 if (index != -1) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001144 final ViewEntry entry = mAdapter.getEntry(index);
Daniel Lehmann0deb6d32010-08-05 11:37:36 -07001145 if (entry != null && entry.intent != null &&
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001146 entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
1147 mContext.startActivity(entry.intent);
1148 return true;
1149 }
1150 } else if (mPrimaryPhoneUri != null) {
1151 // There isn't anything selected, call the default number
1152 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1153 mPrimaryPhoneUri);
1154 mContext.startActivity(intent);
1155 return true;
1156 }
1157 return false;
1158 }
1159
1160 case KeyEvent.KEYCODE_DEL: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001161 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001162 return true;
1163 }
1164 }
1165
Daniel Lehmannc2687c32010-04-19 18:20:44 -07001166 return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001167 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001168
Daniel Lehmann222744e2010-07-12 16:28:06 -07001169 /**
1170 * The listener for the detail loader
1171 */
1172 private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
1173 new LoaderCallbacks<ContactLoader.Result>() {
1174 @Override
1175 public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001176 return new ContactLoader(mContext, mLookupUri, true /* loadGroupMetaData */);
Daniel Lehmann222744e2010-07-12 16:28:06 -07001177 }
1178
1179 @Override
1180 public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
Dmitri Plotnikov5da93632010-09-18 13:09:26 -07001181 if (data == ContactLoader.Result.NOT_FOUND || data == ContactLoader.Result.ERROR) {
Daniel Lehmann222744e2010-07-12 16:28:06 -07001182 // Item has been deleted
1183 Log.i(TAG, "No contact found. Closing activity");
Daniel Lehmann99eefbe2010-08-24 16:45:03 -07001184 if (mListener != null) mListener.onContactNotFound();
Daniel Lehmann222744e2010-07-12 16:28:06 -07001185 return;
1186 }
1187 mContactData = data;
1188 bindData();
1189 }
1190 };
1191
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001192 private ContactDetailHeaderView.Listener mHeaderViewListener =
1193 new ContactDetailHeaderView.Listener() {
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001194 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001195 public void onDisplayNameClick(View view) {
1196 }
1197
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001198 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001199 public void onPhotoClick(View view) {
1200 }
1201 };
1202
1203
1204 public static interface Listener {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001205 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001206 * Contact was not found, so somehow close this fragment. This is raised after a contact
1207 * is removed via Menu/Delete
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001208 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001209 public void onContactNotFound();
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001210
1211 /**
1212 * User decided to go to Edit-Mode
1213 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001214 public void onEditRequested(Uri lookupUri);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001215
1216 /**
1217 * User clicked a single item (e.g. mail)
1218 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001219 public void onItemClicked(Intent intent);
1220
1221 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001222 * User decided to delete the contact
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001223 */
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001224 public void onDeleteRequested(Uri lookupUri);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001225
1226 /**
1227 * User requested creation of a new contact with the specified values.
1228 *
1229 * @param values ContentValues containing data rows for the new contact.
1230 * @param account Account where the new contact should be created
1231 */
1232 public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001233 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001234}