blob: 18c6b6d9765b91f43ec7dd594b9bc4ea5923c72f [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
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts.detail;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070018
19import com.android.contacts.Collapser;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070020import com.android.contacts.Collapser.Collapsible;
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080021import com.android.contacts.ContactLoader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070022import com.android.contacts.ContactOptionsActivity;
23import com.android.contacts.ContactPresenceIconUtil;
24import com.android.contacts.ContactsUtils;
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -070025import com.android.contacts.ContactsUtils.ImActions;
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080026import com.android.contacts.GroupMetaData;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070027import com.android.contacts.R;
28import com.android.contacts.TypePrecedence;
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080029import com.android.contacts.editor.SelectAccountDialogFragment;
Dmitri Plotnikov4597c922010-11-15 13:03:06 -080030import com.android.contacts.model.AccountType;
31import com.android.contacts.model.AccountType.DataKind;
32import com.android.contacts.model.AccountType.EditType;
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080033import com.android.contacts.model.AccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070034import com.android.contacts.util.Constants;
35import com.android.contacts.util.DataStatus;
Dmitri Plotnikov45b44022010-10-15 18:12:09 -070036import com.android.contacts.util.DateUtils;
Daniel Lehmannf77b8e92010-07-16 18:58:36 -070037import com.android.contacts.util.PhoneCapabilityTester;
Dmitri Plotnikov85609ed2010-11-11 15:59:01 -080038import com.android.contacts.widget.TransitionAnimationView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070039import com.android.internal.telephony.ITelephony;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070040
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070041import android.accounts.Account;
Daniel Lehmann25a02822010-05-11 15:52:43 -070042import android.app.Activity;
Daniel Lehmann222744e2010-07-12 16:28:06 -070043import android.app.Fragment;
44import android.app.LoaderManager;
45import android.app.LoaderManager.LoaderCallbacks;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070046import android.content.ActivityNotFoundException;
47import android.content.ContentUris;
48import android.content.ContentValues;
49import android.content.Context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070051import android.content.Entity.NamedContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070052import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050053import android.content.Loader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070054import android.content.res.Resources;
55import android.graphics.drawable.Drawable;
56import android.net.ParseException;
57import android.net.Uri;
58import android.net.WebAddress;
59import android.os.Bundle;
60import android.os.RemoteException;
61import android.os.ServiceManager;
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;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070085import android.view.KeyEvent;
86import android.view.LayoutInflater;
87import android.view.Menu;
88import android.view.MenuInflater;
89import android.view.MenuItem;
90import android.view.View;
Daniel Lehmannc2687c32010-04-19 18:20:44 -070091import android.view.View.OnClickListener;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070092import android.view.View.OnCreateContextMenuListener;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070093import android.view.ViewGroup;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070094import android.widget.AdapterView;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070095import android.widget.AdapterView.OnItemClickListener;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070096import android.widget.BaseAdapter;
Daniel Lehmann18db43d2010-09-30 18:16:59 -070097import android.widget.Button;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070098import android.widget.ImageView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070099import android.widget.ListView;
100import android.widget.TextView;
101import android.widget.Toast;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700102
103import java.util.ArrayList;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700104import java.util.Collections;
105import java.util.List;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700106
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700107public class ContactDetailFragment extends Fragment implements OnCreateContextMenuListener,
108 OnItemClickListener, SelectAccountDialogFragment.Listener {
Dmitri Plotnikov4eeac742010-11-03 16:25:06 -0700109
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700110 private static final String TAG = "ContactDetailFragment";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700111
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700112 private static final int LOADER_DETAILS = 1;
113
Dmitri Plotnikov174fdc22010-10-29 13:55:01 -0700114 private static final String KEY_CONTACT_URI = "contactUri";
Dmitri Plotnikov4eeac742010-11-03 16:25:06 -0700115 private static final String LOADER_ARG_CONTACT_URI = "contactUri";
Dmitri Plotnikov174fdc22010-10-29 13:55:01 -0700116
Daniel Lehmann25a02822010-05-11 15:52:43 -0700117 private Context mContext;
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700118 private View mView;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700119 private Uri mLookupUri;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700120 private Listener mListener;
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700121
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700122 private ContactLoader.Result mContactData;
123 private ContactDetailHeaderView mHeaderView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700124 private ListView mListView;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700125 private ViewAdapter mAdapter;
126 private Uri mPrimaryPhoneUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700127
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700128 private Button mCopyGalToLocalButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700129 private boolean mAllRestricted;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700130 private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700131 private int mNumPhoneNumbers = 0;
Bai Tao09eb04f2010-09-01 15:34:16 +0800132 private String mDefaultCountryIso;
Dmitri Plotnikov85609ed2010-11-11 15:59:01 -0800133 private boolean mContactDataDisplayed;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700134
135 /**
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700136 * Device capability: Set during buildEntries and used in the long-press context menu
137 */
138 private boolean mHasPhone;
139
140 /**
141 * Device capability: Set during buildEntries and used in the long-press context menu
142 */
143 private boolean mHasSms;
144
145 /**
Daniel Lehmann7781ea12010-10-21 21:56:43 -0700146 * Device capability: Set during buildEntries and used in the long-press context menu
147 */
148 private boolean mHasSip;
149
150 /**
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700151 * The view shown if the detail list is empty.
152 * We set this to the list view when first bind the adapter, so that it won't be shown while
153 * we're loading data.
154 */
155 private View mEmptyView;
156
157 /**
158 * A list of distinct contact IDs included in the current contact.
159 */
160 private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
161 private ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
162 private ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
163 private ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
164 private ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
165 private ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
166 private ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
Daniel Lehmann0540f8b2010-10-28 16:55:00 -0700168 private ArrayList<ViewEntry> mRelationEntries = new ArrayList<ViewEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700169 private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
170 private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700171 private LayoutInflater mInflater;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700172
Dmitri Plotnikov85609ed2010-11-11 15:59:01 -0800173 private boolean mTransitionAnimationRequested;
174
Daniel Lehmann25a02822010-05-11 15:52:43 -0700175 public ContactDetailFragment() {
176 // Explicit constructor for inflation
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700177
178 // Build the list of sections. The order they're added to mSections dictates the
179 // order they are displayed in the list.
180 mSections.add(mPhoneEntries);
181 mSections.add(mSmsEntries);
182 mSections.add(mEmailEntries);
183 mSections.add(mImEntries);
184 mSections.add(mPostalEntries);
185 mSections.add(mNicknameEntries);
Daniel Lehmannc2687c32010-04-19 18:20:44 -0700186 mSections.add(mOtherEntries);
Daniel Lehmann0540f8b2010-10-28 16:55:00 -0700187 mSections.add(mRelationEntries);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700188 mSections.add(mGroupEntries);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700189 }
190
191 @Override
Dmitri Plotnikov174fdc22010-10-29 13:55:01 -0700192 public void onCreate(Bundle savedInstanceState) {
193 super.onCreate(savedInstanceState);
194 if (savedInstanceState != null) {
195 mLookupUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
196 }
197 }
198
199 @Override
200 public void onSaveInstanceState(Bundle outState) {
201 super.onSaveInstanceState(outState);
202 outState.putParcelable(KEY_CONTACT_URI, mLookupUri);
203 }
204
205 @Override
Daniel Lehmann25a02822010-05-11 15:52:43 -0700206 public void onAttach(Activity activity) {
207 super.onAttach(activity);
208 mContext = activity;
Bai Tao09eb04f2010-09-01 15:34:16 +0800209 mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(mContext);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700210 }
211
212 @Override
Dianne Hackborn84df2b62010-05-12 19:04:42 -0700213 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700214 mView = inflater.inflate(R.layout.contact_detail_fragment, container, false);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700215
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700216 setHasOptionsMenu(true);
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700217
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700218 mInflater = inflater;
219
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700220 mHeaderView = (ContactDetailHeaderView) mView.findViewById(R.id.contact_header_widget);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700221 mHeaderView.setListener(mHeaderViewListener);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700222
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700223 mListView = (ListView) mView.findViewById(android.R.id.list);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700224 mListView.setOnCreateContextMenuListener(this);
225 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
226 mListView.setOnItemClickListener(this);
227 // Don't set it to mListView yet. We do so later when we bind the adapter.
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700228 mEmptyView = mView.findViewById(android.R.id.empty);
Daniel Lehmann25a02822010-05-11 15:52:43 -0700229
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700230 mCopyGalToLocalButton = (Button) mView.findViewById(R.id.copyLocal);
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700231 mCopyGalToLocalButton.setOnClickListener(new OnClickListener() {
232 @Override
233 public void onClick(View v) {
234 makePersonalCopy();
235 }
236 });
237
Dmitri Plotnikovd9591042010-11-23 09:25:03 -0800238 mView.setVisibility(View.INVISIBLE);
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700239 return mView;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700240 }
241
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700242 public void setListener(Listener value) {
243 mListener = value;
Daniel Lehmann25a02822010-05-11 15:52:43 -0700244 }
245
Dmitri Plotnikov9cd17082010-07-09 14:10:04 -0700246 public Uri getUri() {
247 return mLookupUri;
248 }
249
Daniel Lehmann222744e2010-07-12 16:28:06 -0700250 @Override
251 public void onActivityCreated(Bundle savedInstanceState) {
252 super.onActivityCreated(savedInstanceState);
253
Dmitri Plotnikov4eeac742010-11-03 16:25:06 -0700254 if (mLookupUri != null) {
255 Bundle args = new Bundle();
256 args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri);
257 getLoaderManager().initLoader(LOADER_DETAILS, args, mDetailLoaderListener);
258 }
Daniel Lehmann222744e2010-07-12 16:28:06 -0700259 }
260
Daniel Lehmann25a02822010-05-11 15:52:43 -0700261 public void loadUri(Uri lookupUri) {
Dmitri Plotnikovf42f53d2010-11-03 16:47:53 -0700262 if ((lookupUri != null && lookupUri.equals(mLookupUri))
263 || (lookupUri == null && mLookupUri == null)) {
Dmitri Plotnikov4eeac742010-11-03 16:25:06 -0700264 return;
265 }
266
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700267 mLookupUri = lookupUri;
Dmitri Plotnikov85609ed2010-11-11 15:59:01 -0800268 mTransitionAnimationRequested = mContactDataDisplayed;
269 mContactDataDisplayed = true;
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700270 if (mLookupUri == null) {
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -0800271 getLoaderManager().stopLoader(LOADER_DETAILS);
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700272 mContactData = null;
273 bindData();
274 } else if (getActivity() != null) {
Dmitri Plotnikov4eeac742010-11-03 16:25:06 -0700275 Bundle args = new Bundle();
276 args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri);
277 getLoaderManager().restartLoader(LOADER_DETAILS, args, mDetailLoaderListener);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700278 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700279 }
280
281 private void bindData() {
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700282 if (mView == null) {
283 return;
284 }
285
Dmitri Plotnikov7bf064e2010-11-05 18:08:03 -0700286 if (isAdded()) {
287 getActivity().invalidateOptionsMenu();
288 }
289
Dmitri Plotnikov85609ed2010-11-11 15:59:01 -0800290 if (mTransitionAnimationRequested) {
291 TransitionAnimationView.startAnimation(mView, mContactData == null);
292 mTransitionAnimationRequested = false;
293 }
294
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700295 if (mContactData == null) {
296 mView.setVisibility(View.INVISIBLE);
297 return;
298 }
299
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700300 // Set the header
Daniel Lehmannb2257cf2010-06-15 13:36:07 -0700301 mHeaderView.loadData(mContactData);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700302
303 // Build up the contact entries
304 buildEntries();
305
306 // Collapse similar data items in select sections.
307 Collapser.collapseList(mPhoneEntries);
308 Collapser.collapseList(mSmsEntries);
309 Collapser.collapseList(mEmailEntries);
310 Collapser.collapseList(mPostalEntries);
311 Collapser.collapseList(mImEntries);
312
313 if (mAdapter == null) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700314 mAdapter = new ViewAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700315 mListView.setAdapter(mAdapter);
316 } else {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700317 mAdapter.notifyDataSetChanged();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700318 }
319 mListView.setEmptyView(mEmptyView);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700320
Daniel Lehmann18db43d2010-09-30 18:16:59 -0700321 // Configure copy gal button
322 if (mContactData.isDirectoryEntry()) {
323 final int exportSupport = mContactData.getDirectoryExportSupport();
324 if (exportSupport == Directory.EXPORT_SUPPORT_ANY_ACCOUNT
325 || exportSupport == Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
326 mCopyGalToLocalButton.setVisibility(View.VISIBLE);
327 } else {
328 mCopyGalToLocalButton.setVisibility(View.GONE);
329 }
330 } else {
331 mCopyGalToLocalButton.setVisibility(View.GONE);
332 }
333
Dmitri Plotnikov3ad2f352010-10-28 22:37:51 -0700334 mView.setVisibility(View.VISIBLE);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700335 }
336
337 /**
338 * Build up the entries to display on the screen.
339 */
340 private final void buildEntries() {
Daniel Lehmann7779f492010-08-24 15:26:36 -0700341 mHasPhone = PhoneCapabilityTester.isPhone(mContext);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700342 mHasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
Daniel Lehmann7781ea12010-10-21 21:56:43 -0700343 mHasSip = PhoneCapabilityTester.isSipPhone(mContext);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700344
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700345 // Clear out the old entries
346 final int numSections = mSections.size();
347 for (int i = 0; i < numSections; i++) {
348 mSections.get(i).clear();
349 }
350
351 mRawContactIds.clear();
352
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700353 mAllRestricted = true;
354 mPrimaryPhoneUri = null;
355 mNumPhoneNumbers = 0;
356
357 mWritableRawContactIds.clear();
358
Daniel Lehmann35769b82010-10-18 16:33:29 -0700359 final AccountTypes sources = AccountTypes.getInstance(mContext);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700360
361 // Build up method entries
362 if (mContactData == null) {
363 return;
364 }
365
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700366 ArrayList<String> groups = new ArrayList<String>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700367 for (Entity entity: mContactData.getEntities()) {
368 final ContentValues entValues = entity.getEntityValues();
369 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
370 final long rawContactId = entValues.getAsLong(RawContacts._ID);
371
372 // Mark when this contact has any unrestricted components
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700373 Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
374 final boolean isRestricted = restricted != null && restricted != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700375 if (!isRestricted) mAllRestricted = false;
376
377 if (!mRawContactIds.contains(rawContactId)) {
378 mRawContactIds.add(rawContactId);
379 }
Dmitri Plotnikov4597c922010-11-15 13:03:06 -0800380 AccountType contactsSource = sources.getInflatedSource(accountType,
381 AccountType.LEVEL_SUMMARY);
Daniel Lehmann69e7fec2010-07-20 14:14:56 -0700382 if (contactsSource == null || !contactsSource.readOnly) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700383 mWritableRawContactIds.add(rawContactId);
384 }
385
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700386 for (NamedContentValues subValue : entity.getSubValues()) {
387 final ContentValues entryValues = subValue.values;
388 entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
389
390 final long dataId = entryValues.getAsLong(Data._ID);
391 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
392 if (mimeType == null) continue;
393
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700394 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
395 Long groupId = entryValues.getAsLong(GroupMembership.GROUP_ROW_ID);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700396 if (groupId != null) {
397 handleGroupMembership(groups, mContactData.getGroupMetaData(), groupId);
398 }
399 continue;
400 }
401
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700402 final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
Dmitri Plotnikov4597c922010-11-15 13:03:06 -0800403 AccountType.LEVEL_CONSTRAINTS);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700404 if (kind == null) continue;
405
Daniel Lehmann16249642010-09-10 19:56:51 -0700406 final ViewEntry entry = ViewEntry.fromValues(mContext, mimeType, kind, dataId,
407 entryValues);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700408
409 final boolean hasData = !TextUtils.isEmpty(entry.data);
Dmitri Plotnikov7ccdc1f2010-08-02 18:18:01 -0700410 Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
411 final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700412
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700413 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
414 // Always ignore the name. It is shown in the header if set
415 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700416 // Build phone entries
417 mNumPhoneNumbers++;
Bai Tao09eb04f2010-09-01 15:34:16 +0800418 String phoneNumberE164 =
419 entryValues.getAsString(PhoneLookup.NORMALIZED_NUMBER);
420 entry.data = PhoneNumberUtils.formatNumber(
421 entry.data, phoneNumberE164, mDefaultCountryIso);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700422 final Intent phoneIntent = mHasPhone ? new Intent(Intent.ACTION_CALL_PRIVILEGED,
423 Uri.fromParts(Constants.SCHEME_TEL, entry.data, null)) : null;
424 final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
425 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
426
427 // Configure Icons and Intents. Notice actionIcon is already set to the phone
428 if (mHasPhone && mHasSms) {
429 entry.intent = phoneIntent;
430 entry.secondaryIntent = smsIntent;
431 entry.secondaryActionIcon = kind.iconAltRes;
432 } else if (mHasPhone) {
433 entry.intent = phoneIntent;
434 } else if (mHasSms) {
435 entry.intent = smsIntent;
436 entry.actionIcon = kind.iconAltRes;
437 } else {
438 entry.intent = null;
439 entry.actionIcon = -1;
440 }
441
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700442
443 // Remember super-primary phone
444 if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
445
446 entry.isPrimary = isSuperPrimary;
447 mPhoneEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700448 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
449 // Build email entries
450 entry.intent = new Intent(Intent.ACTION_SENDTO,
451 Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
452 entry.isPrimary = isSuperPrimary;
453 mEmailEntries.add(entry);
454
455 // When Email rows have status, create additional Im row
456 final DataStatus status = mContactData.getStatuses().get(entry.id);
457 if (status != null) {
458 final String imMime = Im.CONTENT_ITEM_TYPE;
459 final DataKind imKind = sources.getKindOrFallback(accountType,
Dmitri Plotnikov4597c922010-11-15 13:03:06 -0800460 imMime, mContext, AccountType.LEVEL_MIMETYPES);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700461 final ViewEntry imEntry = ViewEntry.fromValues(mContext,
Daniel Lehmann16249642010-09-10 19:56:51 -0700462 imMime, imKind, dataId, entryValues);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700463 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
464 if (imActions != null) {
465 imEntry.actionIcon = imActions.getPrimaryActionIcon();
466 imEntry.secondaryActionIcon = imActions.getSecondaryActionIcon();
467 imEntry.intent = imActions.getPrimaryIntent();
468 imEntry.secondaryIntent = imActions.getSecondaryIntent();
469 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700470 imEntry.applyStatus(status, false);
471 mImEntries.add(imEntry);
472 }
473 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
474 // Build postal entries
475 entry.maxLines = 4;
476 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
477 mPostalEntries.add(entry);
478 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
479 // Build IM entries
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700480 final ImActions imActions = ContactsUtils.buildImActions(entryValues);
481 if (imActions != null) {
482 entry.actionIcon = imActions.getPrimaryActionIcon();
483 entry.secondaryActionIcon = imActions.getSecondaryActionIcon();
484 entry.intent = imActions.getPrimaryIntent();
485 entry.secondaryIntent = imActions.getSecondaryIntent();
486 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700487
488 // Apply presence and status details when available
489 final DataStatus status = mContactData.getStatuses().get(entry.id);
490 if (status != null) {
491 entry.applyStatus(status, false);
492 }
493 mImEntries.add(entry);
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700494 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
495 // Organizations are not shown. The first one is shown in the header
496 // and subsequent ones are not supported anymore
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700497 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
498 // Build nickname entries
499 final boolean isNameRawContact =
500 (mContactData.getNameRawContactId() == rawContactId);
501
502 final boolean duplicatesTitle =
503 isNameRawContact
504 && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
505
506 if (!duplicatesTitle) {
507 entry.uri = null;
508 mNicknameEntries.add(entry);
509 }
510 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
511 // Build note entries
512 entry.uri = null;
513 entry.maxLines = 100;
514 mOtherEntries.add(entry);
515 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
David Brown4423ca52010-08-30 18:56:37 -0700516 // Build Website entries
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700517 entry.uri = null;
518 entry.maxLines = 10;
519 try {
520 WebAddress webAddress = new WebAddress(entry.data);
521 entry.intent = new Intent(Intent.ACTION_VIEW,
522 Uri.parse(webAddress.toString()));
523 } catch (ParseException e) {
524 Log.e(TAG, "Couldn't parse website: " + entry.data);
525 }
526 mOtherEntries.add(entry);
David Brown4423ca52010-08-30 18:56:37 -0700527 } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
528 // Build SipAddress entries
529 entry.uri = null;
530 entry.maxLines = 1;
Daniel Lehmann7781ea12010-10-21 21:56:43 -0700531 if (mHasSip) {
532 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
533 Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
534 } else {
535 entry.intent = null;
536 entry.actionIcon = -1;
537 }
David Brown4423ca52010-08-30 18:56:37 -0700538 mOtherEntries.add(entry);
539 // TODO: Consider moving the SipAddress into its own
540 // section (rather than lumping it in with mOtherEntries)
541 // so that we can reposition it right under the phone number.
Daniel Lehmann35769b82010-10-18 16:33:29 -0700542 // (Then, we'd also update FallbackAccountType.java to set
David Brown4423ca52010-08-30 18:56:37 -0700543 // secondary=false for this field, and tweak the weight
544 // of its DataKind.)
Dmitri Plotnikov45b44022010-10-15 18:12:09 -0700545 } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
546 entry.data = DateUtils.formatDate(mContext, entry.data);
547 entry.uri = null;
548 mOtherEntries.add(entry);
Daniel Lehmann0540f8b2010-10-28 16:55:00 -0700549 } else if (Relation.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
550 mRelationEntries.add(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700551 } else {
552 // Handle showing custom rows
553 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
554
555 // Use social summary when requested by external source
556 final DataStatus status = mContactData.getStatuses().get(entry.id);
557 final boolean hasSocial = kind.actionBodySocial && status != null;
558 if (hasSocial) {
559 entry.applyStatus(status, true);
560 }
561
562 if (hasSocial || hasData) {
563 mOtherEntries.add(entry);
564 }
565 }
566 }
567 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700568
569 if (!groups.isEmpty()) {
570 ViewEntry entry = new ViewEntry();
571 Collections.sort(groups);
572 StringBuilder sb = new StringBuilder();
573 int size = groups.size();
574 for (int i = 0; i < size; i++) {
575 if (i != 0) {
576 sb.append(", ");
577 }
578 sb.append(groups.get(i));
579 }
580 entry.mimetype = GroupMembership.MIMETYPE;
Daniel Lehmanncfb525b2010-11-02 19:24:54 -0700581 entry.kind = mContext.getString(R.string.groupsLabel).toUpperCase();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700582 entry.data = sb.toString();
583 mGroupEntries.add(entry);
584 }
585 }
586
587 /**
588 * Maps group ID to the corresponding group name, collapses all synonymous groups.
589 * Ignores default groups (e.g. My Contacts) and favorites groups.
590 */
591 private void handleGroupMembership(
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700592 ArrayList<String> groups, List<GroupMetaData> groupMetaData, long groupId) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700593 if (groupMetaData == null) {
594 return;
595 }
596
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700597 for (GroupMetaData group : groupMetaData) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700598 if (group.getGroupId() == groupId) {
599 if (!group.isDefaultGroup() && !group.isFavorites()) {
600 String title = group.getTitle();
601 if (!groups.contains(title)) {
602 groups.add(title);
603 }
604 }
605 break;
606 }
607 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700608 }
609
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700610 private static String buildDataString(DataKind kind, ContentValues values,
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700611 Context context) {
612 if (kind.actionBody == null) {
613 return null;
614 }
615 CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
616 return actionBody == null ? null : actionBody.toString();
617 }
618
619 /**
620 * A basic structure with the data for a contact entry in the list.
621 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700622 private static class ViewEntry implements Collapsible<ViewEntry> {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700623 public int type = -1;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700624 public String kind;
625 public String typeString;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700626 public String data;
627 public Uri uri;
628 public long id = 0;
629 public int maxLines = 1;
630 public String mimetype;
631
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700632 public Context context = null;
633 public String resPackageName = null;
634 public int actionIcon = -1;
635 public boolean isPrimary = false;
636 public int secondaryActionIcon = -1;
637 public Intent intent;
638 public Intent secondaryIntent = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700639 public ArrayList<Long> ids = new ArrayList<Long>();
640 public int collapseCount = 0;
641
642 public int presence = -1;
643
644 public CharSequence footerLine = null;
645
646 private ViewEntry() {
647 }
648
649 /**
650 * Build new {@link ViewEntry} and populate from the given values.
651 */
652 public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
Daniel Lehmann16249642010-09-10 19:56:51 -0700653 long dataId, ContentValues values) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700654 final ViewEntry entry = new ViewEntry();
655 entry.context = context;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700656 entry.id = dataId;
657 entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
658 entry.mimetype = mimeType;
Dmitri Plotnikov5b201472010-10-13 10:21:08 -0700659 entry.kind = (kind.titleRes == -1 || kind.titleRes == 0) ? ""
Daniel Lehmanna4723252010-10-13 11:17:45 -0700660 : context.getString(kind.titleRes).toUpperCase();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700661 entry.data = buildDataString(kind, values, context);
662
663 if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
664 entry.type = values.getAsInteger(kind.typeColumn);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700665
666 // get type string
667 entry.typeString = "";
668 for (EditType type : kind.typeList) {
669 if (type.rawValue == entry.type) {
Dmitri Plotnikov28ac5842010-11-15 14:20:13 -0800670 if (!type.unspecifiedType) {
671 if (type.customColumn == null) {
672 // Non-custom type. Get its description from the resource
673 entry.typeString = context.getString(type.labelRes);
674 } else {
675 // Custom type. Read it from the database
676 entry.typeString = values.getAsString(type.customColumn);
677 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700678 }
679 break;
680 }
681 }
682 } else {
683 entry.typeString = "";
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700684 }
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700685
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700686 if (kind.iconRes > 0) {
687 entry.resPackageName = kind.resPackageName;
688 entry.actionIcon = kind.iconRes;
689 }
690
691 return entry;
692 }
693
694 /**
695 * Apply given {@link DataStatus} values over this {@link ViewEntry}
696 *
697 * @param fillData When true, the given status replaces {@link #data}
698 * and {@link #footerLine}. Otherwise only {@link #presence}
699 * is updated.
700 */
701 public ViewEntry applyStatus(DataStatus status, boolean fillData) {
702 presence = status.getPresence();
703 if (fillData && status.isValid()) {
704 this.data = status.getStatus().toString();
705 this.footerLine = status.getTimestampLabel(context);
706 }
707
708 return this;
709 }
710
Daniel Lehmann16249642010-09-10 19:56:51 -0700711 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700712 public boolean collapseWith(ViewEntry entry) {
713 // assert equal collapse keys
714 if (!shouldCollapseWith(entry)) {
715 return false;
716 }
717
718 // Choose the label associated with the highest type precedence.
719 if (TypePrecedence.getTypePrecedence(mimetype, type)
720 > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
721 type = entry.type;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700722 kind = entry.kind;
723 typeString = entry.typeString;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700724 }
725
726 // Choose the max of the maxLines and maxLabelLines values.
727 maxLines = Math.max(maxLines, entry.maxLines);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700728
729 // Choose the presence with the highest precedence.
730 if (StatusUpdates.getPresencePrecedence(presence)
731 < StatusUpdates.getPresencePrecedence(entry.presence)) {
732 presence = entry.presence;
733 }
734
735 // If any of the collapsed entries are primary make the whole thing primary.
736 isPrimary = entry.isPrimary ? true : isPrimary;
737
738 // uri, and contactdId, shouldn't make a difference. Just keep the original.
739
740 // Keep track of all the ids that have been collapsed with this one.
741 ids.add(entry.id);
742 collapseCount++;
743 return true;
744 }
745
Daniel Lehmann16249642010-09-10 19:56:51 -0700746 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700747 public boolean shouldCollapseWith(ViewEntry entry) {
748 if (entry == null) {
749 return false;
750 }
751
752 if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
753 entry.data)) {
754 return false;
755 }
756
757 if (!TextUtils.equals(mimetype, entry.mimetype)
758 || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
759 || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
760 || actionIcon != entry.actionIcon) {
761 return false;
762 }
763
764 return true;
765 }
766 }
767
768 /** Cache of the children views of a row */
769 private static class ViewCache {
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800770 public View kindDivider;
771 public View inKindDivider;
772 public View lineBelowLast;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700773 public TextView kind;
774 public TextView type;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700775 public TextView data;
776 public TextView footer;
777 public ImageView actionIcon;
778 public ImageView presenceIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700779 public ImageView secondaryActionButton;
780 public View secondaryActionDivider;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700781 }
782
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700783 private final class ViewAdapter extends BaseAdapter {
Daniel Lehmann16249642010-09-10 19:56:51 -0700784 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700785 public View getView(int position, View convertView, ViewGroup parent) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700786 final ViewEntry entry = getEntry(position);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700787 final View v;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700788 final ViewCache viewCache;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700789
790 // Check to see if we can reuse convertView
791 if (convertView != null) {
792 v = convertView;
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700793 viewCache = (ViewCache) v.getTag();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700794 } else {
795 // Create a new view if needed
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700796 v = mInflater.inflate(R.layout.contact_detail_list_item, parent, false);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700797
798 // Cache the children
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700799 viewCache = new ViewCache();
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700800 viewCache.kind = (TextView) v.findViewById(R.id.kind);
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800801 viewCache.kindDivider = v.findViewById(R.id.kind_divider);
802 viewCache.lineBelowLast = v.findViewById(R.id.line_below_last);
803 viewCache.inKindDivider = v.findViewById(R.id.in_kind_divider);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700804 viewCache.type = (TextView) v.findViewById(R.id.type);
805 viewCache.data = (TextView) v.findViewById(R.id.data);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700806 viewCache.footer = (TextView) v.findViewById(R.id.footer);
807 viewCache.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700808 viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
809 viewCache.secondaryActionButton = (ImageView) v.findViewById(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700810 R.id.secondary_action_button);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700811 viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700812 viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
813 v.setTag(viewCache);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700814 }
815
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700816 final ViewEntry previousEntry = position == 0 ? null : getEntry(position - 1);
817 final boolean isFirstOfItsKind =
818 previousEntry == null ? true : !previousEntry.kind.equals(entry.kind);
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800819 final boolean isLast = position == getCount() - 1;
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700820
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700821 // Bind the data to the view
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800822 bindView(v, entry, isFirstOfItsKind, isLast);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700823 return v;
824 }
825
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800826 protected void bindView(View view, ViewEntry entry, boolean isFirstOfItsKind,
827 boolean isLast) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700828 final Resources resources = mContext.getResources();
829 ViewCache views = (ViewCache) view.getTag();
830
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800831 views.kind.setText(isFirstOfItsKind ? entry.kind : "");
832 views.kindDivider.setVisibility(isFirstOfItsKind ? View.VISIBLE : View.GONE);
833 views.inKindDivider.setVisibility(isFirstOfItsKind ? View.GONE : View.VISIBLE);
834 if (views.lineBelowLast != null) {
835 views.lineBelowLast.setVisibility(isLast ? View.VISIBLE : View.GONE);
Daniel Lehmanncfb525b2010-11-02 19:24:54 -0700836 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700837
Daniel Lehmann5ad20a62010-11-08 16:24:50 -0800838 views.type.setText(entry.typeString);
839 views.type.setVisibility(
840 TextUtils.isEmpty(entry.typeString) ? View.GONE : View.VISIBLE);
841
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700842 // Set the content
843 final TextView content = views.data;
844 if (content != null) {
Bai Taobff55492010-09-26 13:27:36 +0800845 content.setText(entry.data);
Daniel Lehmann0f428ae2010-09-12 18:48:40 -0700846 setMaxLines(content, entry.maxLines);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700847 }
848
849 // Set the footer
850 if (!TextUtils.isEmpty(entry.footerLine)) {
851 views.footer.setText(entry.footerLine);
852 views.footer.setVisibility(View.VISIBLE);
853 } else {
854 views.footer.setVisibility(View.GONE);
855 }
856
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700857 // Set the action icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700858 final ImageView action = views.actionIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700859 if (entry.actionIcon != -1) {
860 Drawable actionIcon;
861 if (entry.resPackageName != null) {
862 // Load external resources through PackageManager
863 actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
864 entry.actionIcon, null);
865 } else {
866 actionIcon = resources.getDrawable(entry.actionIcon);
867 }
868 action.setImageDrawable(actionIcon);
869 action.setVisibility(View.VISIBLE);
870 } else {
871 // Things should still line up as if there was an icon, so make it invisible
872 action.setVisibility(View.INVISIBLE);
873 }
874
875 // Set the presence icon
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700876 final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700877 mContext, entry.presence);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700878 final ImageView presenceIconView = views.presenceIcon;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700879 if (presenceIcon != null) {
880 presenceIconView.setImageDrawable(presenceIcon);
881 presenceIconView.setVisibility(View.VISIBLE);
882 } else {
883 presenceIconView.setVisibility(View.GONE);
884 }
885
886 // Set the secondary action button
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700887 final ImageView secondaryActionView = views.secondaryActionButton;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700888 Drawable secondaryActionIcon = null;
889 if (entry.secondaryActionIcon != -1) {
890 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
891 }
892 if (entry.secondaryIntent != null && secondaryActionIcon != null) {
893 secondaryActionView.setImageDrawable(secondaryActionIcon);
Daniel Lehmannc3a00082010-04-13 13:53:54 -0700894 secondaryActionView.setTag(entry);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700895 secondaryActionView.setVisibility(View.VISIBLE);
896 views.secondaryActionDivider.setVisibility(View.VISIBLE);
897 } else {
898 secondaryActionView.setVisibility(View.GONE);
899 views.secondaryActionDivider.setVisibility(View.GONE);
900 }
901 }
902
903 private void setMaxLines(TextView textView, int maxLines) {
904 if (maxLines == 1) {
905 textView.setSingleLine(true);
906 textView.setEllipsize(TextUtils.TruncateAt.END);
907 } else {
908 textView.setSingleLine(false);
909 textView.setMaxLines(maxLines);
910 textView.setEllipsize(null);
911 }
912 }
913
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700914 private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
Daniel Lehmann16249642010-09-10 19:56:51 -0700915 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700916 public void onClick(View v) {
917 if (mListener == null) return;
918 if (v == null) return;
919 final ViewEntry entry = (ViewEntry) v.getTag();
920 if (entry == null) return;
921 final Intent intent = entry.secondaryIntent;
922 if (intent == null) return;
923 mListener.onItemClicked(intent);
924 }
925 };
926
Daniel Lehmann16249642010-09-10 19:56:51 -0700927 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700928 public int getCount() {
929 int count = 0;
930 final int numSections = mSections.size();
931 for (int i = 0; i < numSections; i++) {
932 final ArrayList<ViewEntry> section = mSections.get(i);
933 count += section.size();
934 }
935 return count;
936 }
937
Daniel Lehmann16249642010-09-10 19:56:51 -0700938 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700939 public Object getItem(int position) {
940 return getEntry(position);
941 }
942
Daniel Lehmann16249642010-09-10 19:56:51 -0700943 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700944 public long getItemId(int position) {
945 final ViewEntry entry = getEntry(position);
946 if (entry != null) {
947 return entry.id;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700948 }
Daniel Lehmann16249642010-09-10 19:56:51 -0700949 return -1;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700950 }
951
952 private ViewEntry getEntry(int position) {
953 final int numSections = mSections.size();
954 for (int i = 0; i < numSections; i++) {
955 final ArrayList<ViewEntry> section = mSections.get(i);
956 final int sectionSize = section.size();
957 if (position < sectionSize) {
958 return section.get(position);
959 }
960 position -= sectionSize;
961 }
962 return null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700963 }
964 }
965
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200966 @Override
Dianne Hackborneb4d1bf2010-06-08 23:12:51 -0700967 public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700968 inflater.inflate(R.menu.view, menu);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700969 }
970
Daniel Lehmann3648d2e2010-06-30 19:58:31 +0200971 @Override
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700972 public void onPrepareOptionsMenu(Menu menu) {
Dmitri Plotnikov7bf064e2010-11-05 18:08:03 -0700973 boolean isEmpty = mContactData == null;
974 boolean isDirectoryEntry = !isEmpty && mContactData.isDirectoryEntry();
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700975
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700976 // Options only shows telephony-related settings (ringtone, send to voicemail).
977 // ==> Hide if we don't have a telephone
978 final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
Daniel Lehmann7779f492010-08-24 15:26:36 -0700979 final boolean deviceHasPhone = PhoneCapabilityTester.isPhone(mContext);
Dmitri Plotnikov7bf064e2010-11-05 18:08:03 -0700980 optionsMenu.setVisible(!isEmpty && !isDirectoryEntry && deviceHasPhone);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700981
982 final MenuItem editMenu = menu.findItem(R.id.menu_edit);
Dmitri Plotnikov7bf064e2010-11-05 18:08:03 -0700983 editMenu.setVisible(!isEmpty && !isDirectoryEntry);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700984
985 final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
Dmitri Plotnikov7bf064e2010-11-05 18:08:03 -0700986 deleteMenu.setVisible(!isEmpty && !isDirectoryEntry);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700987
988 final MenuItem shareMenu = menu.findItem(R.id.menu_share);
Dmitri Plotnikov7bf064e2010-11-05 18:08:03 -0700989 shareMenu.setVisible(!isEmpty && !isDirectoryEntry && !mAllRestricted);
Daniel Lehmannf77b8e92010-07-16 18:58:36 -0700990 }
991
992 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700993 public boolean onOptionsItemSelected(MenuItem item) {
994 switch (item.getItemId()) {
995 case R.id.menu_edit: {
Daniel Lehmann99eefbe2010-08-24 16:45:03 -0700996 if (mListener != null) mListener.onEditRequested(mLookupUri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700997 break;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700998 }
999 case R.id.menu_delete: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001000 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001001 return true;
1002 }
1003 case R.id.menu_options: {
Daniel Lehmann9fe582e2010-10-08 21:15:18 -07001004 if (mContactData == null) return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001005 final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
1006 intent.setData(mContactData.getLookupUri());
1007 mContext.startActivity(intent);
1008 return true;
1009 }
1010 case R.id.menu_share: {
1011 if (mAllRestricted) return false;
Daniel Lehmann9fe582e2010-10-08 21:15:18 -07001012 if (mContactData == null) return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001013
1014 final String lookupKey = mContactData.getLookupKey();
1015 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
1016
1017 final Intent intent = new Intent(Intent.ACTION_SEND);
1018 intent.setType(Contacts.CONTENT_VCARD_TYPE);
1019 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
1020
1021 // Launch chooser to share contact via
1022 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
1023 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
1024
1025 try {
1026 mContext.startActivity(chooseIntent);
1027 } catch (ActivityNotFoundException ex) {
1028 Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
1029 }
1030 return true;
1031 }
1032 }
1033 return false;
1034 }
1035
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001036 private void makePersonalCopy() {
1037 if (mListener == null) {
1038 return;
1039 }
1040
1041 int exportSupport = mContactData.getDirectoryExportSupport();
1042 switch (exportSupport) {
1043 case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY: {
1044 createCopy(new Account(mContactData.getDirectoryAccountName(),
1045 mContactData.getDirectoryAccountType()));
1046 break;
1047 }
1048 case Directory.EXPORT_SUPPORT_ANY_ACCOUNT: {
Daniel Lehmann35769b82010-10-18 16:33:29 -07001049 final ArrayList<Account> accounts = AccountTypes.getInstance(mContext).getAccounts(true);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001050 if (accounts.isEmpty()) {
1051 createCopy(null);
1052 return; // Don't show a dialog.
1053 }
1054
1055 // In the common case of a single writable account, auto-select
1056 // it without showing a dialog.
1057 if (accounts.size() == 1) {
1058 createCopy(accounts.get(0));
1059 return; // Don't show a dialog.
1060 }
1061
Dmitri Plotnikov40bb4812010-11-23 15:21:33 -08001062 final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment();
Daniel Lehmann4a62be42010-09-07 18:22:59 -07001063 dialog.setTargetFragment(this, 0);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001064 dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
1065 break;
1066 }
1067 }
1068 }
1069
1070 @Override
1071 public void onAccountSelectorCancelled() {
1072 }
1073
1074 @Override
Dmitri Plotnikov40bb4812010-11-23 15:21:33 -08001075 public void onAccountChosen(Account account) {
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001076 createCopy(account);
1077 }
1078
1079 private void createCopy(Account account) {
1080 if (mListener != null) {
1081 mListener.onCreateRawContactRequested(mContactData.getContentValues(), account);
1082 }
1083 }
1084
Dmitri Plotnikovd3643142010-07-09 13:49:07 -07001085 @Override
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001086 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001087 if (mListener == null) return;
1088 final ViewEntry entry = mAdapter.getEntry(position);
Daniel Lehmannc3a00082010-04-13 13:53:54 -07001089 if (entry == null) return;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001090 final Intent intent = entry.intent;
1091 if (intent == null) return;
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001092 mListener.onItemClicked(intent);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001093 }
1094
Daniel Lehmann16249642010-09-10 19:56:51 -07001095 public boolean handleKeyDown(int keyCode) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001096 switch (keyCode) {
1097 case KeyEvent.KEYCODE_CALL: {
1098 try {
1099 ITelephony phone = ITelephony.Stub.asInterface(
1100 ServiceManager.checkService("phone"));
1101 if (phone != null && !phone.isIdle()) {
1102 // Skip out and let the key be handled at a higher level
1103 break;
1104 }
1105 } catch (RemoteException re) {
1106 // Fall through and try to call the contact
1107 }
1108
1109 int index = mListView.getSelectedItemPosition();
1110 if (index != -1) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001111 final ViewEntry entry = mAdapter.getEntry(index);
Daniel Lehmann0deb6d32010-08-05 11:37:36 -07001112 if (entry != null && entry.intent != null &&
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001113 entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
1114 mContext.startActivity(entry.intent);
1115 return true;
1116 }
1117 } else if (mPrimaryPhoneUri != null) {
1118 // There isn't anything selected, call the default number
1119 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1120 mPrimaryPhoneUri);
1121 mContext.startActivity(intent);
1122 return true;
1123 }
1124 return false;
1125 }
1126
1127 case KeyEvent.KEYCODE_DEL: {
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001128 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001129 return true;
1130 }
1131 }
1132
Daniel Lehmannc2687c32010-04-19 18:20:44 -07001133 return false;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001134 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001135
Daniel Lehmann222744e2010-07-12 16:28:06 -07001136 /**
1137 * The listener for the detail loader
1138 */
1139 private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
1140 new LoaderCallbacks<ContactLoader.Result>() {
1141 @Override
1142 public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
Dmitri Plotnikov4eeac742010-11-03 16:25:06 -07001143 Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI);
1144 return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */);
Daniel Lehmann222744e2010-07-12 16:28:06 -07001145 }
1146
1147 @Override
1148 public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001149 if (data != ContactLoader.Result.NOT_FOUND && data != ContactLoader.Result.ERROR) {
1150 mContactData = data;
1151 } else {
1152 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
1153 mContactData = null;
Daniel Lehmann222744e2010-07-12 16:28:06 -07001154 }
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001155
Daniel Lehmann222744e2010-07-12 16:28:06 -07001156 bindData();
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001157
1158 if (mContactData == null && mListener != null) {
1159 mListener.onContactNotFound();
1160 }
Daniel Lehmann222744e2010-07-12 16:28:06 -07001161 }
1162 };
1163
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001164 private ContactDetailHeaderView.Listener mHeaderViewListener =
1165 new ContactDetailHeaderView.Listener() {
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001166 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001167 public void onDisplayNameClick(View view) {
1168 }
1169
Daniel Lehmannbe82afa2010-07-14 15:31:56 -07001170 @Override
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001171 public void onPhotoClick(View view) {
1172 }
1173 };
1174
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001175 public static interface Listener {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001176 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001177 * Contact was not found, so somehow close this fragment. This is raised after a contact
1178 * is removed via Menu/Delete
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001179 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001180 public void onContactNotFound();
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001181
1182 /**
1183 * User decided to go to Edit-Mode
1184 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001185 public void onEditRequested(Uri lookupUri);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001186
1187 /**
1188 * User clicked a single item (e.g. mail)
1189 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001190 public void onItemClicked(Intent intent);
1191
1192 /**
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001193 * User decided to delete the contact
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001194 */
Daniel Lehmann69e7fec2010-07-20 14:14:56 -07001195 public void onDeleteRequested(Uri lookupUri);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -07001196
1197 /**
1198 * User requested creation of a new contact with the specified values.
1199 *
1200 * @param values ContentValues containing data rows for the new contact.
1201 * @param account Account where the new contact should be created
1202 */
1203 public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001204 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001205}